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
結論としては, reify か defrecordを使い分けるのが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
- プログラミング言語とシステムデザイン
- Clojureの設計指針 by t_yano.
- namespaceをドメインにするればいい.
- データを関心領域としてそこに対する操作が関数.
- ドメインに操作を関数定義すればいい.
- システムの複雑さはどこから来るのか – Out of the tar pitを読む - Uzabase Tech - by t_yano.
- Clojure Recordを積極的につかう.
- REPLライフをもっと快適に
- Component 単位でシステムをつくるというアイデア, なるほど🤔
- Clojure x ClojureScript で深める Web 開発 Duct x Clean Architecture
- フォルダ構造が参考になる.
Related
- up: 📁Clojure Development
- tags. 🔖Software Design
- refs: