生IntegrantでHTTPサーバーを立てる

Integrantといえばductのバックエンドにある状態管理ライブラリだが、ductを触っているとチラ見えしてくるので、一度生で触ってなんとなく雰囲気が分かるようにしておこうと思う。

Explicity vs Implicity(magic)

Java界に君臨するSpringBootを触ったことがある人は、「これ(だけしか書いてないのに)、なんで動くんだ?」と思ったことがあるはず。なぜなら、SpringBootは裏にあるSpringFrameworkをImplicitにうまいこと設定してくれる仕組みだから。

@SpringBootApplication とか @Controller って書けばWebアプリケーションが起動するって普通に考えてやばい。

HTTPリクエストに応答できるWebアプリケーションを立てるなら、まず確実にTomcatなりJettyが必要だし、DBにつなぐならConnectionインスタンスを生成しておかないと接続ができないはずなのに、起動できてしまう。 やばく便利だし、やばい学習コストがかかる。

いっぽうIntegrantやその他のClojure製状態管理ライブラリはExplicityに重心を置いているものが多いように思う。 つまり、明示的に指定しないとアプリケーション内で使えるようにならないし、そこには明示的に渡した引数しか渡せない。

Getting Started of Integrant

というわけでIntegrantを使ってみる。

lein new app gs-integrant

IntegrantはClojure 1.9.0が必要なのでClojureのバージョンを上げる。 そしてIntegrantと、HTTPリクエストに応答できるようにringを依存関係に追加する。

(defproject gs-integrant "0.1.0-SNAPSHOT"
  ;; ...
  :dependencies [[org.clojure/clojure "1.9.0"]
                 [integrant "0.6.2"]
                 [ring "1.6.3"]]
  ;; ...
  )

REPLを起動してエディターから接続する。

GitHub - weavejester/integrant: Micro-framework for data-driven architecture

公式のREADMEに書いてあるとおりにdefmethodする。

(ns gs-integrant.core
  (:gen-class)
  (:require [integrant.core :as ig]
            [ring.adapter.jetty :as jetty]
            [ring.util.response :as resp]))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println "Hello, World!"))

(def config
  {:adapter/jetty {:port 8080 :handler (ig/ref :handler/greet)}
   :handler/greet {:name "alice"}})

(defmethod ig/init-key :adapter/jetty [_ {:keys [handler] :as opts}]
  (jetty/run-jetty handler (-> opts (dissoc :handler) (assoc :join? false))))

(defmethod ig/halt-key! :adapter/jetty [_ server]
  (.stop server))

(defmethod ig/init-key :handler/greet [_ {:keys [name]}]
  (fn [_] (resp/response (str "Hello " name))))

gs-integrant.core名前空間を評価して、REPLで移動して

(def system
  (ig/init config))

を評価するとHTTPサーバーが起動し、localhost:8080 にアクセスすると Hello alice と表示される。 同じ名前空間(ig/halt! system) すると終了する。

ここではinit-keyとhalt-keyしか書いていないが、それ以外にもsuspend-key!とresume-keyがあって、それぞれ中断時の振る舞い、restart時の振る舞いを定義できる。特に中断時は、引数を一度atomを経由して持つことで、サーバーの再起動なしにその内容を変更することができたりする。やばい。

これでhandlerにrouter機能をもつcompojureなりbidiなりを放り込めばルーティングができるんだろうし、cljsのコンパイルタスクを追加すればrestartするたびにコンパイルされるんだろうし、なんてシンプルなんだろう。

小さなモジュールがシーケンスを通して連携し合う世界観を活用しているし、またその世界観を拡張もしているクールなライブラリ。