『Domain Modeling Made Functional』という関数型ドメインモデリングの本を読んでみた。とても面白かったので紹介したい。
はじめに
著者について
『Domain Driven Design with the F# type System』 という面白いスライドで、関数型のテクニックを使って BLOBA1 をエレガントにコーディングする技法を提案していた Scott Wlaschin という人。
おすすめしたい人
特に次のような人は、きっと面白く読めるのではないかと思う。
- ふだん純粋関数型言語を使っていて計算機科学的な面での良さは理解しているが、応用範囲としては特定用途のライブラリ(パーサライブラリなど)やミドルウェアなどに留まったままで、従来オブジェクト指向が扱ってきたような、大規模&複雑なビジネスドメインでの活用がイメージしにくい人。
- オブジェクト指向ベースの従来型ドメイン駆動開発をすでに実践しているが、そろそろ命令型パラダイム2から宣言型パラダイムにシフトしたい人。特に、マルチパラダイムな言語を使っているうちに関数型の良さが分かってきて、さらに重心を移したい人。3
※ ちなみにコード例は F#だけど、F#を知らない自分にも普通に読めた。
ここが面白い
自分が特に面白いと思うのは以下の点
-
型の重視
- つねに型を重視して、型に牽引させるようにして開発を進めるスタイルが採用されている。Idris 界隈でいう Type Driven Development4 と同じ方向性。この本では依存型までは踏み込んでいないが、代わりに適用範囲を BLOBA にまで広げている。
- コーディングだけではなく、非プログラマ含む関係者との意識合わせに関連するプラクティスにおいても、型による表現を活用している。
- 従来の OOP 的なイメージだと、型といえば Value Object や Entity の型を思い浮かべがちになるが、ワークフローやそれを構成するステップや関数も、型として把握される。特に、代数的データ型(ADT: Algebraic Data Type)が活用される。
-
イベントとワークフローへのフォーカス
- データ構造よりもイベント/ワークフローをとにかく重視する。
- 本書で描写される Event Storming の場面でも、参加者から持ち寄られたイベントが関連付けられるのは、Aggregate ではなくワークフローになる。
- 小さくて純粋な関数を合成してワークフローを構成する。
- ワークフローはパイプラインとして説明されている。Scala だとFS2 などの streaming I/O library との親和性が高い感じ。
-
自然言語ベースのモデリング
- イベントもワークフローも、適度に書式化された自然言語を使って記述する。型の記述に UML を使ったり、ワークフローの記述に BPMN などを使ったりはしない。
- テキストベースの記述という点で、『Writing Effective Use Cases』で述べられていた Alistair Cockburn のユースケース・シナリオに近いものがあるし、Dave Thomas の『The Pragmatic Programmer』TIP 20、"Keep knowledge in plain text" にも一致していて、賛同できるところが大きい。
各章のハイライト
全体の構成としては、第一部ドメインの理解、第二部ドメインのモデリング、第三部モデルの実装と続く(現実の開発場面では、細かいサイクルで反復、あるいは同時進行することも、ちゃんと注釈されている)。以下、各章ごとに概要と面白かったところを順に紹介したい。
第一部 Understanding the Domain
第1章 Introducing Domain-Driven Design
関係者一同でメンタルモデルを共有するためのガイドラインが提示される。
- データ構造よりもイベントとワークフローに着目すること
- 問題領域でドメインをサブドメインに分割すること
- ソリューション領域で文脈や境界に注意してドメインをモデル化すること
- メンタルモデル共有のための共通言語を作ること
このためのツールとして、Domain、Subdomain、Domain Model、Bounded Context、Context Map、Core Domain/Supported Domain/Generic Subdomain、Ubiquitous Language、Domain Event、といったおなじみの DDD の概念が、比較的新しい Event Storming といったプラクティスとともに紹介される。
簡潔な図と言葉による説明で、各概念の意味も目的もわかりやすい。特に、FPの観点から捉え直された Event Storming が面白くて、すでに上でも書いたように、オリジナルの Event Storming と違ってイベントは Aggregate ではなくワークフローに関連づけられる。
第2章 Understanding the Domain
第1章のガイドラインの通り、本書ではワークフローに着目するため、database-driven、database-first な分析は却下。また従来の OOA/D でやってきたような、要件からクラスを抽出するといった class-driven な手法も却下。もちろん ER図も UML も使わない。
ワークフローといっても BPMN のようなダイアグラムは使わず、プログラマ以外の関係者でも読んで認識を共有できるように、自然言語を若干書式化した程度のプレーンテキスト(mini-language)で表現する。ここでユビキタス言語が活用される。
第3章 A Functional Architecture
Onion Architecture や、C4 model といったアーキテクチャ関連のアイデアが紹介される。
DDD の概念として、Shared Kernel、Customer/Supplier、Conformist、Anti-Corruption Layer などが導入され、既出の Bounded Context や Context Map も、アーキテクチャの観点から改めて解説される。
関連して Microservice についても若干述べられるが、本書ではあまり積極的には推奨されていない。いわゆる Microservice Premium が高くつきすぎたり、失敗して Distributed Monolith になったりしがちで、そう簡単ではないとの説明。どうしても必要になったときに、後で切り出すのが良かろうというスタンス。
第ニ部 Modeling the Domain
第2部では、第1部で理解・共有したドメインを、型で表現してモデリングする。
第4章 Understanding Types
代数的データ型(ADT:Algebraic Data Type)が、OR(Sum Type)と AND(Product Type)で構成されることなど、基礎的な知識の解説。
第5章 Domain Modeling with Typs
前章で解説した ADT の、イベントとワークフローへの適用。ここでも TDD (Type Driven Development) が徹底される。
DDD 関連の概念では、Value Object、Entity、Aggregate などがここで紹介され、一意性の有無や、永続化の単位、トランザクション境界などの基本事項が解説される。
また、Entity や Aggregate がイミュータブルでも全く問題ないことについても、関数型プログラミングの観点から改めて確認される。
第6章 Integrity and Consistency in the Domain
Integrity と Consistency の定義などから始まる章。
"make illegal states unrepresentable" という標語が提示される。例えば Boolean には 真と偽しかないように、不正な値が取りえないように型を表現すること。ビジネスルールをこのように型で表現すると "trust boundary" の中では不正データがそもそも存在し得なくなり、いわゆる「防御的プログラミング」も無用になる。
複数コンテキスト間の境界をまたいだ一貫性については、Gregor Hohpe の記事『Starbucks Does Not Use Two-Phase Commit』を引き合いに、大抵は Eventual Consistency で事足りると主張される。(この Hophe の記事もおもしろい。)
第7章 Modeling Workflows as Pipelines
小さなステップで構成されたパイプラインでワークフローを表現する、transformation oriented programming というパラダイムが紹介される。
またワークフロー内の State Machine を型で表現する技法もここで解説される。状態を表すための真偽値フラグや列挙値は撤廃。また「非同期」や「エラー(の可能性)」といった effect(文脈/作用/効果)も型で表現される。
人手や他システムとのインタラクションが介在するような、実行スパンの長いワークフロー(Saga)に関連して、状態の永続化や、一定以上に複雑化した場合の Process Manager の導入などの技法が述べられる。
第三部 Implementing the Model
型で表現されたモデルを関数として実装する第三部。
第8章 Understanding Functions
まず OO と FP の実装方針の基本的な違いが語られるが、「高階関数やラムダをたまに少しだけ使う程度の関数使用は FP とは言わない」という強めの主張もあって面白い。
後の章で解説される、小さな関数からワークフローを組み立てる道具立てとして、高階関数、カリー化、部分適用、全関数/部分関数、合成 といった、関数型プログラミングの基礎的な技法について紹介される。
「全関数」について、「定義域中のある値で例外が生じるなら、そのシグネーチャはウソになる」という言い方で解説されるが、ここでも型による表現が強調される。
第9章 Implementation: Composing a Pipeline
ワークフロー中の各ステップを接続する手法として、adapter function、lifting、partial application などが解説される。
この本では、関数型プログラミングにおける DI として、原始的な partial application が採用されているが、その先に Reader Monad や Free Monad があるなども軽く言及されている。
第10章 Implementation: Working with Errors
エラーの分類として、Domain Errors、Panics、Infrastructure Errors の 3つが解説される。
また、副作用だけで情報を返さない、dead end function(DB書き込み、キューへのポスト、fire-and-forget 等)の扱いについても、ここで説明。
Monad / Applicative についてもざっくり解説。この本の独自呼称の two-track モデル あるいは railroad-oriented programming というアイデアが紹介されるが、Scala や Haskell の Either や EitherT を使った合成を、線路っぽいイメージで表現したもの。
第11章 Serialization
Bounded Contex 内の Domain Object を境界外でやり取りする際に、プリミティブな型やコレクションで構成する DTO に変換する手法などが紹介される。この章は、概ね F# 固有の技法に関連する内容。
第12章 Persistence
persistence ignorant という重要キーワードが提示される。ドメインモデルの内部には、永続化にまつわる情報を混入させるべきではない という大原則の確認(実に良い言葉)。
永続化ガイドラインとして以下の3つ
- 永続化をエッジに押しやること (Repository パターンも不要になる)
- CQS、CQRS、Event Sourcing を活用して、Read と Write を切り離すこと(型も別途定義)
- 各 Bounded Context にそれぞれ別途データストレージをもたせること
CQRS、Event Sourcing については、ドメインモデリングの下層で技術的な難所が多いけど、この本ではあまり詳しく解説されてない。
第13章 Evolving a Design and Keeping It Clean
No plan survives contact with the enemy という米軍の言い習わしの紹介から始めて、変化を取り込みながら big ball of mud になるのを避けつつ、デザインを進化させるためにはどうするかを論じた章。
下記の4つの状況について解説されている。
- ワークフローに新しいステップが追加される場合
- ワークフローのインプットが変更される場合
- ドメインの主要なオブジェクトの型が変わる場合
- ワークフロー全体が変わる場合
どの場合でも、関数型ドメインモデリング+関数型プログラミングならば変更のリスクが小さいという説明。実務では、運用中に作られた旧データのマイグレーションなども難所だと思うけど、たぶん本の主旨の範囲外なので詳説はされていない。
Memo
- 関数型ドメインモデリングの本だと、『Functional And Reactive Domain Modeling』という Scala の良書もあって、こっちは、Kleisli、Free Monad、Lens といった中級以上の技法もどんどん推してくスタイル。
- この本の関数型プログラミング技法は初級レベルに抑えられていたけど、上級者は、もうちょい高度な技法でワークフロー/パイプラインを実装すると面白いかもしれない。
- 第一部でメンタルモデルを共有するために使ったテキストベースの "mini-language"は、別の記事で紹介してみたい。
-
Boring Lines of Business Applications. ビジネスアプリケーションの退屈なソースコード行 ↩
-
型駆動開発。『Type Driven Development in Idris』という依存型言語 Idris の入門書で提示されていたもの。この本では Vector やソートなど基本的なデータ構造とアルゴリズムに適用するに留まっていた。Test Driven Development をもじった言葉。 ↩