주로 쓰는 개발 프레임워크는 Node.js 이다. 그래서 백앤드를 구성하는 20개 남짓의 microservice들은 모두 노드로 구현돼 있다. 서비스 간 통신을 위해 만든 라이브러리가 HTTP와 Redis로 구현돼 있기 때문에 프로토콜만 맞출 수 있다면 서비스를 구현할 때 어떤 언어든지 고려해볼 수 있다. 그래서 내가 만든 몇 개의 서비스들 중에서 가장 중요한 서비스를 테스트 삼아 Clojure로 구현해 보았다. 처음부터 끝까지 혼자 다 만든 서비스라서 Node.js로 구현된 로직을 Clojure로 다시 쓰는 데에는 오랜 시간이 걸리지 않았고 성능도 비슷한 수준으로 나왔다. 오히려 다시 쓰기 시작하면서 구조가 더 명확해지고 눈엣가시 같던 구현들도 제거할 수 있었으니 상당히 만족스러웠다. 그 상태에서 두 개 서비스를 더 Clojure로 다시 썼고 충분히 좋은 성능을 보여주었기 때문에 프로덕션 릴리즈를 해보고 싶었다. 하지만 백앤드 팀의 유일한 Clojure 개발자라서 혼자 모든 것을 다 해야 하는 상황이 팀에게도 나에게도 이상적이지 않았다. 제법 여럿이 흥미를 보였지만 더 발전되지 못했고 지금은 아무도 찾지 않는 저장소가 되고 말았다.
최근에 Spark을 공부하면서 Scala를 써보려고 했는데, Clojure 만큼은 아니란 생각이 들었다. Functional에 왜 OOP를 가져다 붙였는지 잘 이해할 수가 없다. 언어들이 서로 좋은 것들을 배껴와 서로 닮아 가는 건 알겠는데 그럴 거면 Java를 더 functional 하게 만들지 하는 생각이 들었다. 그래서 관심이 식어버렸다. 다시 Clojure를 꺼내 들었는데 하나도 기억이 안나서 처음 했던 것처럼 간단한 headless 서버를 만들어 보기로 했다. 이번에 쓸 프레임워크는 Pedestal이다.
이곳의 코드를 참고했다.
https://github.com/pedestal/pedestal/tree/master/samples/hello-world
프로젝트 생성
다음 명령을 실행하여 프로젝트를 생성할 수 있다.
lein new pedestal-service echo-service
다음과 같이 폴더와 파일들을 생성한다.
echo-service
- src
- services
- echo.clj
- ping.clj
- server.clj
코드 수정
각 파일에 들어갈 코드는 다음과 같다.
1 2 3 4 5 | ;; echo.clj (ns echo-server.services.echo) (defn handler [request] (let [message (get-in request [:params :message] "Echo")] {:status 200 :body (str message "\n")})) | cs |
1 2 3 4 | ;; ping.clj (ns echo-server.services.ping) (defn handler [request] {:status 200 :body (str "pong\n")}) | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | ;; project.clj (ns echo-server.server (:gen-class) (:require [io.pedestal.http :as http] [io.pedestal.http.route :as route] [io.pedestal.http.body-params :as body-params] [io.pedestal.http.route.definition :refer [defroutes]] [echo-server.services.ping :as ping] [echo-server.services.echo :as echo])) (defroutes routes [[["/" ["/ping" {:get ping/handler}] ["/echo" {:get echo/handler}]]]]) (def service {:env :prod ::http/routes routes ::http/resource-path "/public" ::http/type :jetty ::http/port 8080}) (defonce runnable-service (http/create-server service)) (defn run-dev "The entry-point for 'lein run-dev'" [& args] (println "\nCreating dev server") (-> service (merge {:env :dev ::http/join? false ::http/routes #(deref #'routes) ::http/allowed-origins {:creds true :allowed-origins (constantly true)}}) http/default-interceptors http/dev-interceptors http/create-server http/start)) (defn -main "The entry-point for 'lein run'" [& args] (println "\nCreating prod server") (http/start runnable-service)) | cs |
자동으로 생성되는 파일명을 변경했기 때문에 project.clj 파일을 다음과 같이 일부 수정해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ;; project.clj (defproject echo-service "0.5.1" :description "Simple hello-world service in Pedestal" :url "http://pedestal.io/samples/index" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.8.0"] [io.pedestal/pedestal.service "0.5.1"] [io.pedestal/pedestal.jetty "0.5.1"] [ch.qos.logback/logback-classic "1.1.7" :exclusions [org.slf4j/slf4j-api]] [org.slf4j/jul-to-slf4j "1.7.21"] [org.slf4j/jcl-over-slf4j "1.7.21"] [org.slf4j/log4j-over-slf4j "1.7.21"]] :min-lein-version "2.0.0" :resource-paths ["config", "resources"] :profiles {:dev {:aliases {"run-dev" ["trampoline" "run" "-m" "hello-world.server/run-dev"]} :dependencies [[io.pedestal/pedestal.service-tools "0.5.1"]]}} :main echo-server.server) | cs |
실행
이후 다음 명령으로 서버를 실행시킨다.
lein run
HTTP 서버를 개발 모드에서 실행시키는 방법은 다음과 같다.
lein repl
echo-server.server=> (def serv (run-dev))
동작 확인
서버가 정상적으로 동작하는지 보기 위해 cURL을 이용한다.
curl -i "localhost:8080/ping"
curl -i "localhost:8080/echo?message=hello"
참고
https://github.com/pedestal/pedestal
https://nordicapis.com/10-frameworks-for-building-web-applications-in-clojure/