Skip to content

Instantly share code, notes, and snippets.

@manmyung

manmyung/clojure_intro_4.md Secret

Last active Jan 18, 2017
Embed
What would you like to do?

이전 시간에 언급하지 못한 내용

프로젝트 다루기

클로저로 실제 프로그램을 만들 때는 주로 프로젝트 단위로 한다.

프로젝트 생성

$ lein new app my-stuff
  • new: 프로젝트를 생성하는 작업
  • app: 독립형 프로그램을 만들 때 많이 쓰이는 템플릿
  • my-stuff: 프로젝트 이름

프로젝트 실행

$ cd my-stuff
$ lein run

명령창에 "Hello, World!"가 찍힌다.

프로젝트 수정

src/my_stuff/core.clj 파일을 편한 에디터로 연다. 다음과 같은 내용이다.

(ns my-stuff.core ; (1)
  (:gen-class))

(defn -main ; (2)
  "I don't do a whole lot ... yet."
  [& args]
  (println "Hello, World!"))
  • (1) my-stuff.core라는 이름공간을 선언한다. 이름공간은 이름이 겹치지 않게 관리하기 위한 방법이다.
  • (2) -main은 프로젝트에서 처음으로 실행되는 함수이다.

이제, "Hello, World!""Hello, Clojure!"처럼 원하는 문자열로 바꾸고 파일을 저장하자.

프로젝트 다시 실행

$ lein run

다시 실행하면 바꾼 문자열이 찍힌다.

프로젝트 빌드

lein run을 사용하지 않고도 단독으로 실행할 수 있는 jar 파일을 만들어 보자.

$ lein uberjar

빌드가 끝나면 다음처럼 실행할 수 있다.

$ java -jar target/uberjar/my-stuff-0.1.0-SNAPSHOT-standalone.jar

마찬가지로 바꾼 문자열이 찍힌다.

프로젝트에서 REPL 사용하기

프로젝트 내에서 REPL을 실행하면 그 프로젝트와 연관된 작업을 REPL에서 할 수 있다.

$ lein repl

다음을 실행하자. 그러면 프롬프트가 기존의 user=>이 아니라 my-stuff.core=>임을 확인할 수 있다. 이것은 현재 my-stuff.core라는 이름공간 안에 있음을 의미한다.

my-stuff.core 이름공간 안에 있으므로 그 안에서 정의한 -main 함수를 바로 사용할 수 있다. 실행해 보자.

my-stuff.core=> (-main)
Hello, Clojure!
nil

Hello, Clojure!println에 의한 화면 출력이고, nil-main 함수의 반환값이다.

간단한 웹 어플리케이션 실습

미니 방명록 만들기

프로젝트 생성

$ lein new compojure mini-guestbook

여기의 compojure는 웹 프로그램을 만들 때 많이 사용하는 템플릿이다.

웹 서버 실행

$ cd mini-guestbook
$ lein ring server

브라우저에서 새 창이 열리면서 "Hello World" 글자가 나타난다. 최소한의 웹 서버를 만든 것이다!

코드 확인

src/mini_guestbook/handler.clj를 열어보자. 다음과 같다.

(ns mini-guestbook.handler
  (:require [compojure.core :refer :all] ; (1)
            [compojure.route :as route] ; (1)
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]))

(defroutes app-routes ; (2)
  (GET "/" [] "Hello World") ; (3)
  (route/not-found "Not Found")) ; (4)

(def app
  (wrap-defaults app-routes site-defaults))
  • (1) Compojure를 불러와서 사용한다. Compojure는 라우팅을 제공하는 라이브러리이다.
  • (2) HTTP 라우트를 정의하는 구문이다.
  • (3) '/' 경로에 대해서 "Hello World" 문자열로 응답하는 Get 라우트를 만든다.
  • (4) 정의되어 있지 않는 주소에 대해서는 "Not Found" 문자열로 응답하는 라우트를 만든다.

사실 Compojure는 Ring에 라우팅 기능만 제공하는 작은 라이브러리이다. Ring은 클로저 웹 어플리케이션의 근간으로, 요청과 응답을 클로저 맵으로 처리하는 라이브러리이다. 기본적인 웹 서버(Jetty)를 포함하고 있고, 다른 종류의 웹 서버도 쉽게 붙일 수 있다. 어떤 웹 서버를 붙여도 Ring은 인터페이스 역활을 해서, 동일한 방식으로 웹 요청을 처리할 수 있다.

Hiccup 라이브러리 추가

"Hello World" 이외에 좀 더 복잡한 HTML을 만들기 위해 우리는 Hiccup이라는 라이브러리를 사용한다. Hiccup은 클로저에서 HTML을 생성하는 가장 간단한 방법으로 클로저 자료구조를 HTML로 변환해 준다. 클로저에서 라이브러리를 사용하려면 project.clj의 :dependencies에 추가해야 한다. [hiccup "1.0.5"]를 추가하자. 수정한 project.clj는 다음과 같다.

(defproject mini-guestbook "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [compojure "1.4.0"]
                 [ring/ring-defaults "0.1.5"]
                 [hiccup "1.0.5"]]
  :plugins [[lein-ring "0.9.7"]]
  :ring {:handler mini-guestbook.handler/app}
  :profiles
  {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                        [ring/ring-mock "0.3.0"]]}})

REPL에서 이 라이브러리를 테스트해보자.

$ lein repl

다음처럼 실행한다.

user=> (require '[hiccup.core :as h]) ; (1)
nil
user=> (h/html [:h1 "Hello Word"]) ; (2)
"<h1>Hello Word</h1>"
  • (1) 이름공간을 불러오는 또 다른 방법이다.
  • (2) 클로저 자료구조를 HTML로 변환했다.

화면 만들기

이제, 방명록 화면을 만들기 위해 src/mini_guestbook/handler.clj를 수정하자. 수정한 파일은 다음과 같다.

(ns mini-guestbook.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [hiccup.core :refer (html)]
            [hiccup.form :as form]
            [hiccup.page :as page]
            [ring.util.anti-forgery :as anti-forgery]))

(defn add-form [] ; (3)
  (form/form-to [:post "/add"]
             (anti-forgery/anti-forgery-field)
             (form/text-area {:rows 2 :cols 30} "message") [:br]
             (form/submit-button "전송")))

(defn messages []
  '({:message "테스트1"} {:message "테스트2"})) ; (5)

(defn message-list [] ; (4)
  [:ul
   (map (fn [x] [:li (:message x)])
        (messages))])

(defn index [] ; (2)
  (html
    [:head
     [:title "방명록"]]
    [:body
     [:h1 "방명록"]
     (add-form)
     [:h2 "메시지"]
     (message-list)]))

(defroutes app-routes
           (GET "/" [] (index)) ; (1)
           (route/not-found "Not Found"))

(def app
  (wrap-defaults app-routes site-defaults))

새로운 라이브러리를 추가했기 때문에 웹 서버를 다시 시작해야 한다. lein ring server를 실행하여 화면이 어떻게 바뀌었는지 보자.

  • (1) 기존 "Hello World" 대신 Hiccup으로 생성한 HTML을 제공하기 위해 index 함수를 호출한다.
  • (2) Hiccup으로 전체 HTML을 만든다.
  • (3) 방명록 입력 form을 만든다.
  • (4) 방명록 메시지를 리스트 형식으로 보여준다.
  • (5) 테스트를 위해 messages를 반환해서 미리 방명록 메시지를 보여주도록 했다.

화면 꾸미기

CSS로 화면을 약간 더 이쁘게 만들자. resources/public 디렉토리에 다음 내용의 styles.css 파일을 만들자.

body {
    background-color: Cornsilk;
}

textarea {
    font-size: 120%;
}

input[type=submit] {
    width: 60px;
    margin: 5px 0px;
    font-size: 120%;
}

h1 {
    color: CornflowerBlue;
}

이 css 파일을 HTML에 적용하기 위해 src/mini_guestbook/handler.clj 파일의 index 함수에 (page/include-css "/styles.css")를 다음처럼 추가하자.

(defn index []
  (html
    [:head
     [:title "방명록"]
     (page/include-css "/styles.css")]
    [:body
     [:h1 "방명록"]
     (add-form)
     [:h2 "메시지"]
     (message-list)]))

브라우저 창을 다시 로딩하자. 화면이 바뀌는 것을 볼 수 있다.

전송한 메시지를 받는 라우트 추가

브라우저의 방명록 화면에서 방명록을 작성한 후 전송해보자. 그러면 "Not Found"가 보인다. 이유는 작성한 메시지를 add-form 함수에서 /add 주소에 POST 형식으로 전송했는데, 이에 대응하는 라우트가 아직 없기 때문이다. 코드를 추가하자.

(ns mini-guestbook.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [hiccup.core :refer (html)]
            [hiccup.form :as form]
            [hiccup.page :as page]
            [ring.util.anti-forgery :as anti-forgery]
            [ring.util.response :as resp])) ; (3)

...

(defn add [message]
  (println message) ; (2)
  (resp/redirect "/")) ; (3)

(defroutes app-routes
           (GET "/" [] (index))
           (POST "/add" [message] (add message)) ; (1)
           (route/not-found "Not Found"))

...
  • (1) /add 경로에 POST 형식의 메시지를 받는 라우트를 추가했다.
  • (2) 명령창에서 메시지를 찍도록 수정했다.
  • (3) 처리가 끝난 후, / 경로로 다시 가도록 수정했다.

브라우저에서 http://localhost:3000/ 주소의 방명록 화면으로 가서 "안녕하세요"라는 메시지를 전송하면 명령창 화면에 그 메세지가 출력되는 것이 보인다.

$ lein ring server
2015-12-06 15:14:32.729:INFO:oejs.Server:jetty-7.6.13.v20130916
2015-12-06 15:14:32.767:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:3000
Started server on port 3000
안녕하세요

데이터베이스 초기 설정

받은 메시지를 저장하기 위해 데이터베이스를 사용한다. H2라는 작은 SQL 데이터베이스를 사용하여 파일 형식으로 저장한다. 그리고 프로그램에서 H2와 연결하기 위해 clojure.java.jdbc라는 라이브러리를 사용한다. project.clj의 :dependencies에 관련 라이브러리를 추가하자.

  :dependencies [[org.clojure/clojure "1.7.0"]
                 [compojure "1.4.0"]
                 [ring/ring-defaults "0.1.5"]
                 [hiccup "1.0.5"]
                 [org.clojure/java.jdbc "0.4.2"]
                 [com.h2database/h2 "1.4.188"]]

REPL에서 데이터베이스의 초기 설정을 하자.

$ lein repl

jdbc 라이브러리를 불러들이자.

user=> (require '[clojure.java.jdbc :as sql])
nil

h2 접속 정보를 h2-db라는 이름으로 만들어둔다.

user=> (def h2-db {:dbtype "h2"
                   :dbname "./mini_guestbook_h2"})
#'user/h2-db

guestbook 테이블을 만들자. 이때 mini_guestbook_h2.mv.db라는 데이터베이스 파일이 생긴다.

user=> (sql/db-do-commands h2-db
                             "CREATE TABLE guestbook (message VARCHAR(128))")
(0)

테스트를 위해 메시지 2개를 데이터베이스에 넣자.

user=> (sql/insert! h2-db
                    :guestbook {:message "hello1"})
(nil)
user=> (sql/insert! h2-db
                    :guestbook {:message "hello2"})
(nil)

메세지가 잘 들어갔는지 확인해보자.

user=> (sql/query h2-db
                  "select * from guestbook")
({:message "hello1"} {:message "hello2"})                  

잘 된다.

데이터베이스의 메시지 가져와서 보이기

다음처럼 수정한다.

(ns mini-guestbook.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [hiccup.core :refer (html)]
            [hiccup.form :as form]
            [hiccup.page :as page]
            [ring.util.anti-forgery :as anti-forgery]
            [ring.util.response :as resp]
            [clojure.java.jdbc :as sql])) ; (1)

(def h2-db {:dbtype "h2" ; (2)
            :dbname "./mini_guestbook_h2"})

...

(defn messages []
  (sql/query h2-db ; (3)
             "select * from guestbook"))

...
  • (1) jdbc 라이브러리를 불러들인다.
  • (2) h2 연결 정보를 h2-db라는 이름으로 만든다.
  • (3) 기존 테스트 메시지를 없애고, 데이터베이스에서 메시지를 가져온다.

lein ring server를 다시 실행하면, 메시지가 기존의 "테스트1", "테스트2"에서 "hello1", "hello2"로 바뀐 것을 확인할 수 있다.

입력한 메시지를 데이터베이스에 저장하기

add 함수를 다음처럼 수정한다.

(defn add [message]
  (println message)
  (sql/insert! h2-db
               :guestbook {:message message})
  (resp/redirect "/"))

브라우저 창을 다시 로딩한다. 이제 새로운 방명록 메시지를 전송하면 밑에 추가된다.

미니 방명록 완성!

완성된 화면

mini-guestbook

전체 코드

project.clj

(defproject mini-guestbook "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [compojure "1.4.0"]
                 [ring/ring-defaults "0.1.5"]
                 [hiccup "1.0.5"]
                 [org.clojure/java.jdbc "0.4.2"]
                 [com.h2database/h2 "1.4.188"]]
  :plugins [[lein-ring "0.9.7"]]
  :ring {:handler mini-guestbook.handler/app}
  :profiles
  {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                        [ring/ring-mock "0.3.0"]]}})

src/mini_guestbook/handler.clj

(ns mini-guestbook.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [hiccup.core :refer (html)]
            [hiccup.form :as form]
            [hiccup.page :as page]
            [ring.util.anti-forgery :as anti-forgery]
            [ring.util.response :as resp]
            [clojure.java.jdbc :as sql]))

(def h2-db {:dbtype "h2"
            :dbname "./mini_guestbook_h2"})

(defn add-form []
  (form/form-to [:post "/add"]
             (anti-forgery/anti-forgery-field)
             (form/text-area {:rows 2 :cols 30} "message") [:br]
             (form/submit-button "전송")))

(defn messages []
  (sql/query h2-db
             "select * from guestbook"))

(defn message-list []
  [:ul
   (map (fn [x] [:li (:message x)])
        (messages))])

(defn index []
  (html
    [:head
     [:title "방명록"]
     (page/include-css "/styles.css")]
    [:body
     [:h1 "방명록"]
     (add-form)
     [:h2 "메시지"]
     (message-list)]))

(defn add [message]
  (println message)
  (sql/insert! h2-db
               :guestbook {:message message})
  (resp/redirect "/"))

(defroutes app-routes
           (GET "/" [] (index))
           (POST "/add" [message] (add message))
           (route/not-found "Not Found"))

(def app
  (wrap-defaults app-routes site-defaults))

참고

다음을 참고하여 강의안을 만들었다.

요약

  • 프로젝트
    • 생성
    • 실행
    • 수정
    • 빌드
    • REPL
  • 간단한 웹 어플리케이션
    • 생성
    • 웹서버 실행
    • Ring, Compojure
    • Hiccup
    • 화면 만들기
    • 화면 꾸미기
    • 라우트 추가
    • 데이터베이스 설정
    • 데이터베이스 사용

안내

설문

https://docs.google.com/forms/d/17Q7jrxd_gP5PyOHVQWe-8_FFOmAeB6DiOVZdJDLQBbM/viewform

뒷풀이

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.