1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

IntegrantとIntegrant-REPLで:duct/constの機能を実装してみた

Last updated at Posted at 2019-06-14

こんにちは、@na4daです。

Clojureのライフサイクル管理ライブラリDuctには、config内で定数に名前を与えることができるduct/constがあります。その機能をIntegrantIntegrant-REPLで実装する機会があったため、そのメモになります。忘備録的な内容のため、具体的な実装は省いています。

duct/constの使い方

Ductを用いる際に、configファイル(EDN形式)の中で

{...
 [:duct/const :some.example/foo]
 "path/to/foo.txt"
 [:duct/const :some.example/bar]
 "path/to/bar.txt"
 ...}

このように記述することで、以下のようにconfigファイルの別の場所から:duct/constで与えたキー名で参照することができます。

{...
 :some.ns/load-files
 {:foo #ig/ref :some.example/foo
  :bar #ig/ref :some.example/bar}
 ...}

これらが読み込まれて正常に解釈されると、初期化時にメソッド:some.ns/load-filesへ渡される実際のデータが以下のようになります。

[:some.ns/load-files {:foo "path/to/foo.txt" :bar "path/to/bar.txt"}]

手順

任意のnamespaceにDuctで導入されているduct/constの中身をそのまま記述します。ディスパッチ値は、namespace付きの任意のものを与えます。

;; namespace: some.ns

;;; const
;; ::const => :some.ns/const
(defmethod integrant.core/init-key ::const [_ v] v)

任意のディスパッチ値をキーとして使い、configを記述します。

{...
 [:some.ns/const :some.example/foo]
 "path/to/foo.txt"
 [:some.ns/const :some.example/bar]
 "path/to/bar.txt"
 ...}

configで用いるキーがディスパッチ値に対応していれば、このまま適切に初期化をすることで読み込むことができます。以下は例として、configをconfig.edn内に記述した状態でのconfigの初期化を行うコードです。

(def ^:private config-file
  "path/to/config.edn")

(defn load-config
  [config]
  (-> config
      clojure.java.io/resource
      slurp
      integrant.core/read-string
      (doto integrant.core/load-namespaces)))
       
(integrant.repl/set-prep! #(load-config config-file))

ここではintegrant.repl/set-prep!を用います。これはIntegrant-REPLで初期化する際に、下準備として行いたい処理を記述した関数を与えることができる関数です。この記述はもともとlein new ductで作成されるテンプレートで用いられている書き方になります(参考)。また、load-configにはduct/prep-config内でも用いられているload-namespacesを適用しています。

このように初期化することで、config内では以下のようにして参照することができます。

{...
 [:some.ns/const :some.example/foo]
 "path/to/foo.txt"
 [:some.ns/const :some.example/bar]
 "path/to/bar.txt"

 ...

 :another.ns/example-handler
 {:foo #ig/ref :some.example/foo
  :bar #ig/ref :some.example/bar}
 ...}

独自の名前をつける

configで用いるキーをディスパッチ値と別のものにしたい場合、例えば

{...
 [:def :some.example/foo]
 "path/to/foo.txt"
 [:def :some.example/bar]
 "path/to/bar.txt"
 ...}

このように、config上では:defと表記しつつ:some.ns/constを適用させたい場合は、load-configの中で、(integrant.core/read-string)の直後にwalkで文字列置換を行います。

(def ^:private config-file
  "path/to/config.edn")

(def ^:private replace-map
  {:def :some.ns/def})

(defn load-config
  [config]
  (let [postwalk-replace* (partial clojure.walk/postwalk-replace replace-map)]
    (-> config
        clojure.java.io/resource
        slurp
        integrant.core/read-string
        postwalk-replace*
        (doto integrant.core/load-namespaces))))
       
(integrant.repl/set-prep! #(load-config config-file))

ここでは、replace-mapが、ちょうど文字列置換の対応表の役割を担うmapになっています。

終わりに

Clojureでライフサイクル管理を導入した上で手軽に開発を行う場合は、もっぱらIntegrantの機能を内包したDuctを使うケースが大半だと思います。使いどころがあまり無いかもしれませんが、もし参考になりましたら幸いです。

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?