真のミニマルフレームワーク duct


Ring, compojure, hiccupでお馴染みのweavejester先生の新作フレームワークがductです。

ductはclojureらしさを、存分に活かしたフレームワークでclojureの良さを十分に堪能できるかと思います。

ductの特長

REPL-driven development

開発はすべてREPLの中でやるように設計されています。

user=> (go)
:started
user=> (reset)
:reloading (charset-mapper.endpoint.example charset-mapper.system charset-mapper.config charset-mapper.main charset-mapper.endpoint.example-test user)
:resumed

(go)でRingサーバを起動し、(reset)で再起動しソースの変更を反映できます。オートリロードは見たところ標準ではついてなさそうです。

Component

ググらビリティの低い"Component"とは、状態の一元管理とライフサイクルの記述を担うフレームワークです。状態管理を各ネームスペースのatomやrefでやり始めると、散らばってどこになにがあるかわからなくなるし、前述のREPLでサーバ再起動したりする際に、状態の適切なクリアと再初期化ができないとうまく動かなくなってしまうのでComponentが作られたようです。

ductでもComponentをフル活用しています。というかductはComponentの薄いラッパーに過ぎません。とにかくweavejester先生の、高機能かつ小さいライブラリや薄いラッパーを作る能力は感動ものです。

Componentではsystemというマップに、すべての状態を押し込めます。

(component/system-map
  :app  (handler-component (:app config))
  :http (jetty-server (:http config))
  :cdi  (cdi-component)
  :example (endpoint-component example-endpoint)
  :api (endpoint-component api-endpoint))

Componentの依存関係があるときは、using関数を使って宣言しておくと、フレームワーク側がいい順番で初期化してくれるようになります。
このusingを全てのコンポーネントに対して定義するために、system-using関数が使えます。

スレッディングマクロでつなげて書くとこんな感じです。

(-> (component/system-map
     :app  (handler-component (:app config))
     :http (jetty-server (:http config))
     :example (endpoint-component example-endpoint))
    (component/system-using
     {:http [:app]
      :app  [:example]}))

duct用のコンポーネントを作ってみる

後述のExampleアプリで、Component作ってみました。JavaEEのJPAを使うので、CDIコンテナを起動・停止するためのものです。

(defrecord CDI []
  component/Lifecycle
  (start [component]
    (if (:weld component)
      component
      (let [weld (Weld.)]
        (.initialize weld)
        (assoc component :weld weld))))
  (stop [component]
    (if-let [weld (:weld component)]
      (do (.shutdown weld)
          (dissoc component :weld))
      component)))

(defn cdi-component []
  (->CDI))

Weldの起動・停止しかしてないので、この程度でかけちゃいます。

ductのアプリケーションを作ってみる

Leiningenテンプレートがあるので、さくっと作れます。必要な機能が選択式であるのはLuminusあたりと同じです。

⇒  lein new duct myduct +example +cljs 
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=lcd
Retrieving duct/lein-template/0.5.5/lein-template-0.5.5.pom from clojars
Retrieving duct/lein-template/0.5.5/lein-template-0.5.5.jar from clojars
Generating a new Duct project named myduct...
Run 'lein setup' in the project directory to create local config files.

この状態でreplを起動すれば、すぐにWebサーバを起動できる状態にあります。

2015-12-02 15:40:29.094:INFO::main: Logging initialized @12654ms
nREPL server started on port 46827 on host 127.0.0.1 - nrepl://127.0.0.1:46827
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.7.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_66-b17
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (go)
Compiling "target/figwheel/myduct/public/js/main.js" from ["src" "dev"]...
notifying browser that file changed:  target/figwheel/myduct/public/js/goog/deps.js
notifying browser that file changed:  target/figwheel/myduct/public/js/cljs_deps.js
notifying browser that file changed:  out/cljs/user.js
Successfully compiled "target/figwheel/myduct/public/js/main.js" in 7.073 seconds.
:started

これで、Ringサーバが起動し、ブラウザからhttp://localhost:3000/example+exampleで足したExampleアプリケーションにルーティングされて、レスポンスが返ってきます。

+cljsを付けると、Clojurescriptのベースも出力されます。REPLで、

user=> (reset)

とやると、Clojurescriptのファイルを編集していれば、同時にコンパイルされ、Figwheelの機能を使って、ブラウザ側もライブリロードされます。

ここから先は特にductに特別なことはありません。compujureなどを使ってWebアプリケーションを作るのと同じです。

ductを使ったアプリケーション

ductを使って作ってみました。

アプリケーションの機能自体については、12/14のシステムエンジニア Advent Calendarで書く予定です。

omとductで簡単なシングルページアプリケーションを実装してあります。

ductへコントリビュートしてみた

+cljsを付けてlein newすると、プロジェクト配下にJavaファイルを作ったときにコンパイル対象にならない、というところでハマったので、初めてweavejester先生にプルリクを送ってみました。

履歴を追うとわかるのですが、懇切丁寧にコミットメッセージの書き方指導までしていただきました

まとめ

weavejester先生のことなので、ductは多機能の方向には進まず、このままミニマルな立ち位置を貫くことでしょう。したがって、フルスタック指向の人たちにはマッチしないと思われます。このミニマル/シンプルさはClojureの醍醐味なので、それを十分に堪能したいという人は、触れてみると面白いかと思います。