DuctでHello World

これは [Clojure アドベントカレンダー 2017](https://qiita.com/advent-calendar/2017/clojure) 5日目の記事です。

`lein new duct <duct-app>` してみたけど、そのあとどうすれば良いのか分からないといった方の参考になればと思い、書かせていただきます。

> 対象読者: これからDuctを使い始めたい方

なお、私の手元の環境は Windows10 Pro です。

それでは始めます。

## `lein new duct <app-name>` はダメ！ゼッタイ！

まず、Ductのlein-templateですが、使わないことをお勧めします。
生成されるファイルが多く混乱のもととなるため、最初は１からプロジェクトを作りましょう。

## フォルダ構成

lein-templateは使わずにファイルとフォルダを手動で作成します。
以下の構成でファイルとフォルダを作成してください。

```
foo-clj
├── project.clj
├── resources
│   └── config.edn
└── src
    ├── foo_clj
    │   └── endpoint
    │       └── example.clj
    └── user.clj
```

Ductのlein-templateは推奨しませんが、素のlein-templateであればOKです。`lein new <app-name>` でプロジェクトを作成していただいてもかまいません。

## project.cljの中身

project.cljを以下のようにします。ちょっとDuctなどのバージョンが古いですが、私の手元で確認したものとなります。気になる方はぜひ最新バージョンでお試しください。

```clj:project.clj
(defproject foo-clj "0.0.1"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [duct/core "0.3.3"]
                 [duct/module.logging "0.2.0"]
                 [duct/module.web "0.5.0"]
                 [integrant/repl "0.2.0"]])
```

## user.cljの中身

次にuser.cljを以下のようにします。これにより、replから前処理`(prep)`や初期化処理`(init)`、終了処理`(halt)`などが行えるようになります。

```clj:src/user.clj
(ns user
  (:require [integrant.repl :refer [clear halt go init prep reset]]
            [duct.core :as duct]
            [clojure.java.io :as io]
            [integrant.repl.state :refer [config system]]))      

(defn read-config []
  (duct/read-config (io/resource "config.edn")))
   
(integrant.repl/set-prep! (comp duct/prep read-config))
```

## config.ednの中身

次にconfig.ednを以下のようにします。今後の設定に関してはこちらのファイルに色々書いていく形となります。

```clj:resources/config.edn
{:duct.core/project-ns foo-clj
 :duct.core/environment :development
 :duct.module/logging 
 {}
 :duct.module.web/site
 {}
 :duct.router/cascading
 [#ig/ref :foo-clj.endpoint/example]
 :foo-clj.endpoint/example
 {}}
```

## example.cljの中身

さいごにexample.cljを以下のようにします。こちらはCompojureを使って書いていく形となります。

```clj:src/foo_clj/endpoint/example.clj
(ns foo-clj.endpoint.example
  (:require [compojure.core :refer :all]
            [integrant.core :as ig]))
    
(defn example-endpoint [options]
  (context "/example" []
    (GET "/" [] "<b>Hello, World!</b>")))

(defmethod ig/init-key :foo-clj.endpoint/example [_ options]
  (example-endpoint options))
```

defmethodの部分はintegrantによるものです。

## REPLによる動作確認

Ductを動かすための最低限の準備は整いました。REPLから下記コマンドを実行して、jetty-serverを起動します。

```shell-session
PS D:\dev\foo-clj> lein repl
...
user=> (prep)  # 前処理を実行
:prepped
user=> (init)  # 初期化処理を実行
:duct.server.http.jetty/starting-server {:port 3000}
:initiated
```

以上で、サーバの起動は完了です。Webブラウザから`http://localhost:3000/example`にアクセスしてみましょう。

![foo-clj.PNG](https://qiita-image-store.s3.amazonaws.com/0/5085/d7d6f4e2-3e4e-b561-b1f3-98c44b83555b.png)

正常にページが表示されればOKです！

あとはページを増やすなり、Hiccupやenliveを導入するなり好きにカスタマイズしてください。

## 応用1: endpointの追加

endpointの追加はconfig.ednに記載します。

```clj:resources/config.edn
{:duct.core/project-ns foo-clj
 :duct.core/environment :development
 :duct.module/logging 
 {}
 :duct.module.web/site
 {}
 :duct.router/cascading
 [#ig/ref :foo-clj.endpoint/example
  #ig/ref :foo-clj.endpoint/example2]  # new!
 :foo-clj.endpoint/example
 {}
 :foo-clj.endpoint/example2            # new!
 {}}
```
:duct.router/cascadingへの登録と空のendpointの定義がセットで必要となります。
設定を変更したらreplから`(reset)`を実行してください。

```shell-session
user=> (reset)
:reloading ()

user=> java.lang.IllegalArgumentException: No method in multimethod 'init-key' for dispatch value: :foo-clj.endpoint/example2
        clojure.lang.ExceptionInfo: Error on key :foo-clj.endpoint/example2 when building system
```

errorが発生しますが、これはendpointの実態を作成していないためです。exampleを参考にexample2を作成して`(reset)`すれば無事新しいendpointが公開されます。

## 応用2: ポート番号の変更

jetty-serverのデフォルトポートが3000番ですが、別のポート番号に変更してみます。config.ednに以下の行を追加します。

```clj:resources/config.edn
{:duct.core/project-ns foo-clj
 :duct.core/environment :development
 :duct.module/logging 
 {}
 :duct.module.web/site
 {}
 :duct.router/cascading
 [#ig/ref :foo-clj.endpoint/example
  #ig/ref :foo-clj.endpoint/example2]
 :foo-clj.endpoint/example
 {}
 :foo-clj.endpoint/example2
 {}
 :duct.server.http/jetty    # new!
 {:port    3001}}           # new!
```

変更後は`(reset)`すれば反映されます。

Ductを使う場合ソースコードに変更を加えて`(reset)`で反映というサイクルで開発を進めていくことができます。

## 参考情報

Ductを使うにはintegrantは欠かせないものとなりますので、まずはintegrantを習得しましょう。
ぜひ、作成者である[@weavejester](https://github.com/weavejester) (James Reeves)さんのskillcastをご覧になってください。

[Enter Integrant: a micro-framework for data-driven architecture with James Reeves](https://skillsmatter.com/skillscasts/9820-enter-integrant-a-micro-framework-for-data-driven-architecture-with-james-reeves)

[追加1]
Duct Frameworkを使用する場合、個々のmoduleの設定を理解しておく必要もあるため、そちらのリンクも追加しておきます。

[server.http.jetty](https://github.com/duct-framework/server.http.jetty)
[module.web](https://github.com/duct-framework/module.web)
[module.logging](https://github.com/duct-framework/module.logging)

[追加2]
Ductテンプレートを推奨しないと書きましたが、構造を理解すればdev環境とprod環境に分けた設計を行えるようになるため、慣れてくればテンプレートを活用すると良いでしょう。そのときは作者である[@weavejester](https://github.com/weavejester) (James Reeves)さんのブログをご参照ください。

[Advancing Duct](https://www.booleanknot.com/blog/2017/05/09/advancing-duct.html)
[Building Web Services with Duct](https://www.booleanknot.com/blog/2017/05/29/building-web-services-with-duct.html)

上記のブログでduct-alphaのテンプレートを使用しておりますが、12/5現時点ではductで大丈夫かと思います。(すべて試してませんが) duct-alphaにてプロジェクトを作成したところ少し古いバージョンでproject.cljが生成されてしまいました。

## おわり

以上です。12/4に Clojure アドベントカレンダー 2017 を見たらたまたま12/5が空いていたため、即興で書かせていただきました。今後も加筆修正は行っていく予定ですので、誤っている点や改善点などございましたらコメントいただければ幸いです。