Clojure Architecture

Clojureのアプリケーション設計.

Clojureを書ける人は昔Javaの人のことが多いので, このへんの設計の話題も詳しい人が多い.

Clojure Design Topics

💡Mapのprimitive filed vs computed field問題

💡少ない抽象データ構造とたくさんの関数, 💡Everything is a MapというOOPに対するアンチテーゼClojureの設計思想がある. すべてはマップとその操作関数でシンプルに表現するということは美しいものの, 実際のモデリングでしばしばめんどくさいことになっている. 特に以下のディスカッションは大変なことなっている.

https://groups.google.com/g/clojure/c/v03o5MWys9E

{:first-name "Alice", :last-name "Beasley"}

MapにはkeyでメンバにアクセスできるのでOOPのようなgetterを書くべきでないというのは解る. しかし, full-nameが欲しくなったときにとたんにめんどくさくなる.

(defn get-full-name [p]
  (str (:first-name p) " " (:last-name p)))

データ操作の関数でMapを操作するという思想からはget-full-nameを定義するのは自然だが, first-name, last-nameはkeywordでアクセスしてfull-nameだけ関数というのは統一感がいまいち.

これを一般化すると, primitive vs computed fieldの問題となる.


関数を定義してRecordも同一のnamespaceにbindingするのがしておくのが一番かんたんなのだが, 操作するときにいちいちnamespaceをrequireして関数を利用する必要があるのが面倒.

Clojure prefers “simple” solutions over “easy” solutions.

ref. Simple made Easy

という思想を持ち出してMapにbindingするべきだという意見. full-nameをMapのfieldに持たせる時, それをMapの生成時に計算するか, アクセスするまで遅延評価にしておくかというパターンがある.

(defn build-user [first-name last-name]
  {:first-name first-name
   :last-name last-name
   :full-name (str first-name " " last-name)})
 
(defn build-user [first-name last-name]
  {:first-name first-name
   :last-name  last-name
   :full-name  #(str (:first-name %) " " (:last-name %))})

ここでのポイントは, Mapの生成時に計算するか, 必要になるまで計算しないか.


このfull-nameはシンプルなパターンだが, よりモデルが複雑になると, protocolやreifyの出番かもしれない. これをすべて包括的に議論しているのが以下.

https://github.com/plumatic/eng-practices/blob/master/clojure/20130926-data-representation.md

結論としては, reifydefrecordを使い分けるのがSimpleかつClojureらしいとのこと. データに着目するならばdefrecord, 振る舞いに着目するならばreify. 両者の違いはカプセル化(transparent).


Mapのデータ構造をドメインモデルとして定義するような📝Clean Architectureの観点では, defrecordとkeyword accessorが美しいのかもしれない. しかし, 名前からして複雑な計算結果によって値を取得する場合はinterfaceとしてのreifyがいいのか, defrecordを提示したnamespaceにhelper functionとして関数定義がいいのかも.

美しさやシンプルさに正解はなく, 場合によって判断するのが望ましい.

also. 💡util関数がRecordにあるとClojureらしい(SimpleでElegant)ならばプロトコル実装

Clojure Clean Architecture実践

各レイヤごとのClojureの視点からの特徴.

  • domain
    • Clojureは値はすべて定数ということを活かせる.
    • Clojure Mapは素で強力な力があるのでこれを活用する.
  • usecase
  • infrastructure
    • 自律しているサービス, loop構造をもつもの.
    • その本質は状態であり, このLayer飲み状態管理ライブラリで管理する.
  • interface
    • clojure.spec による外部との境界チェック.
    • Clojureは動的言語だがSpecを使うことで必要なところのみ型の恩恵を組み込む.
      • 外部と内部の境界をレイヤで明確に分離すれこそSpecの導入の価値あり.

tags: 📝Clean Architecture

分類ためのラベルの定義にはnamespace + defをつかう

ref. 🤔スコープを制限する目的ならばRecordではなくMapをつかう

References