“1. Introducing Domain-Driven Design” from “Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#” 📚
ソフトウェア開発者の仕事とは:
- not only コードを書くこと
- but also ソフトウェアを通して問題を解決すること
要件が不明瞭だったり、設計が悪ければコーディングをどれだけ頑張ろうとよい価値は得られない
- つまり garbage in, garbage out
- 📝 この表現はソフトウェア自体への入力と出力に対して使われることが多いが、ここでは input が「要件」、output を「得られる価値」というかなり高レイヤな意味で使われている
“garbage in” を減らすのに必要なもの:
- 明解なコミュニケーション
- 共有ドメイン知識に基づく設計アプローチ
ドメイン駆動設計(DDD)をによってこれらを実現できる。
共有モデルの重要性
まず開発者が問題を正しく理解することが重要
とりうるアプローチを 3 つ紹介:
- 仕様書・要件定義書を整備する 💣
- ドメインエキスパートを開発プロセスに入れる ⚠️
- ドメイン駆動設計 ✅
1. 仕様書・要件定義書を整備する 💣
これはだめなやり方。
理由:
- ドメインエキスパートと開発チームの距離が遠くなってしまう ❌
- “伝言ゲーム” のようなことが実際に起こり、これはプロジェクトにおいては致命的 ❌
2. ドメインエキスパートを開発プロセスに組み込む ⚠️
いわゆるアジャイルなやり方。一見良さそうに思えるがベストではない。
誤りをすぐに修正できるようにするために、ドメインエキスパートを開発プロセスのフィードバックループに組み入れる。
デメリット:
- 開発者の働きがドメインエキスパートのメンタルモデルの単なる翻訳者でしかなくなってしまう ❌
- 必ず抜け落ちてしまう情報がある ❌
- 将来、ドメインエキスパートからの入力なしにコードをメンテする開発者は容易に方針を誤ってしまう ❌
3. ドメイン駆動設計 ✅
プロジェクトに関わる人々(下記)が共通のメンタルモデルを共有した状態で開発を進める:
- ドメインエキスパート
- 開発者
- ステークホルダー
ソフトウェアのコードはメンタルモデルを直接反映したものになるので、DDD では"翻訳"という作業は存在し得ない。
メリット:
- 市場投入スピード ✅
- 得られるビジネス価値の大きさ ✅
- 無駄の排除 ✅
- スムーズなメンテナンス ✅
(コラムでは、開発者自身がドメインエキスパートになる重要性が示されている)
共有メンタルモデルを作るためのガイドライン:
- データ構造ではなく、ビジネスイベントとワークフローに着目せよ
- ドメインを小さいサブドメインに分割せよ
- それぞれのサブドメインについてモデルを作れ
- 会話やコードで使われる共通言語を作れ
ビジネスイベントからドメインを理解する
共有メンタルモデルの構築はどこから始めたらいい? → イベントから始めよ ✅データ構造から始めるな ❌
理由:
- ビジネスがデータをただ持っているだけで終わることは有り得ないため
- ビジネスは何らかの方法でデータを変換するため
ビジネスプロセスとは、データやドキュメントの変換処理の集まり → これらの変換がどう行われ、どう関連しているかを理解することが重要
使われずにただ置かれているだけのデータは何の価値も生まない。 これらのデータから価値を引き出すためのプロセスはどうやって始まるか? → ドメインイベントによって始まる
ドメインイベントの例:
- 外部トリガー
- スケジュールに基づいたトリガー
- 観測に基づくトリガー
ドメインイベントは事実である変化しないため必ず過去形で表現される。
(中略)
下記の 2 節で、イベントストーミングの説明と実例が書いてある:
- Using Event Storming to Discover the Domain
- Discovering the Domain: An Order-Taking System
イベントストーミング自体を理解するには提唱者の解説を読むほうがいい。
イベントストーミングで重要な点:
- 「“我々” vs “彼ら” 思考」から脱却できること
- 意見が合わなかったら、それは問題ではなくチャンス
イベントを境界にまで拡張する
要件の取りこぼしを防ぐために下記に注意を払う:
- 最初のイベントと思っていたイベントをトリガーする他のイベントがないか
- 最後のイベントと思っていたイベントにトリガーされる後続のイベントはないか
全てを一気に変える必要はない。最も価値を生む一部分から取りかかれ。
コマンドを記述する
イベントストーミングによってイベントが見えるようになった。 それらのイベントはどのように発生するのか? → そのイベントが起こることを誰かが要求したから
- 全てのイベントがコマンドに紐付いている必要はない。スケジュールによるトリガーや監視に基づくトリガーなどがあるため。
DDD ではこれらの要求を「コマンド」という。 コマンドは常に命令形で表現される。 紛らわしいが、OO のコマンドパターンとは違う。
コマンドが成功するとワークフローがトリガーされ、ドメインイベントが作られる。
例:
- 「X を起こせ」というコマンドによってワークフローが X を起こし、「X が起こった」というドメインイベントが発行される
- 「注文書を会社 A に送れ」というコマンドによってワークフローが注文書を送ると、「注文書が送信された」というドメインインベントが発行される
- コマンド「注文を確定せよ」 → ドメインイベント「注文が確定された」
- コマンド「配送を顧客 B に送れ」 → 「配送が送られた」
📝 「配送が送られた」は日本語としておかしいが、DDD ではモデルを記述する用語(ユビキタス言語)が重要なのでこのままにしておく。原著が英語なので限界がある。自然な日本語にしようとして「配送」の代わりに「商品」とするのは絶対に NG。いずれにせよ、ドメインエキスパートが使う言葉に合わせることが重要。
これから、コマンド、ワークフロー、イベントの組み合わせでビジネスプロセスを記述していく。 これはまさに関数プログラミングの動きと同じ。
ドメインをサブドメインに分ける
イベントストーミングによってイベントとコマンドを洗い出したことで、多数のビジネスプロセスの中身を理解できた状態になった。 しかし全体像がまだ整理されていない。 コードを書き始める前に整理する必要がある。
本文中では、受注プロセスを例として分割の様子が示されている。
受注プロセスとは:
- 受注
- 配送
- 請求
- その他
部署が既に分かれていれば、サブドメイン分割の強力な手がかりとなる。
ドメインとは? → DDD において、一貫した知識領域のこと。 …わかりにくい。 言い換えると、ドメインエキスパートが専門性を持っている領域。 この理解はかなり実用的。なぜなら辞書で「請求」を調べて頭を悩ませるのではなく、シンプルに「請求部門のドメインエキスパートがやっていること」と表現できるようになるため。
ドメインは重複しうるため、分割する際には注意が必要。 はっきり分けたくなるが、現実世界はそれほど単純ではない。
ドメインは少し重複しているので、例えば受注部門の担当者は請求部門や配送部門の業務を少しだけ知っている必要がある。
境界づけられたコンテキストを使って解決策を作る
ドメインの情報全てを反映する必要はない。 得られた情報の中から、解こうとしている問題に関するもののみを抽出する。ほとんどは無関係なはず
なので「問題領域」と「解決領域」を分け、両者を別物として扱う必要がある:
- 問題領域は現実世界
- 解決領域はドメインモデル
問題領域は設計プロセスを経て解決領域になる
なぜ「サブシステム」でなく「境界づけられたコンテキスト」という表現なのか?
- 理由: 解決策を考える際に、本当に重要なものに集中できるようになるため
コンテキストと境界を常に意識せよ。
-
コンテキストが重要な理由: それぞれのコンテキストは解決策に独自の知識を反映するから コンテキスト内では言語は共通し、デザインも一貫しているが、これらはコンテキストの外では使えなくなる
-
境界が重要な理由: 現実世界では境界はぼやけているが、ソフトウェアの場合にはそれぞれを独立して開発できるようにするために分割したい。ソフトウェアでは API を使うなどして達成できるが、裏を返せばそのように作ったドメインモデルは現実世界に比べて情報量が減るということ。しかしこれは複雑さを減らし、メンテナンス性を手に入れるためのトレードオフ
問題領域のドメインは必ずしも解決領域のコンテキストと一対一対応しない:
- あるドメインが複数の境界づけられたコンテキストに分割されることもある
- 複数のドメインが一つの境界づけられたコンテキストになるケースもある(レガシーソフトウェア)
- 例: ある企業が「受注」と「請求」をひとまとめに扱うシステムを既にインストールしていた場合、このレガシーシステムと連携するにはそれを一つの境界づけられたコンテキストとして扱う必要がある
ドメインを分けるなら、それぞれの境界づけられたコンテキストが明確な責務を持つ必要がある
- 理由: モデルを実装するとき、境界づけられたコンテキストはそのままソフトウェアコンポーネント(DLL、サービス、名前空間など)になるため
コンテキストを正しくする
聞くは易し、実行するは難し。 というか DDD で1番難しいのがこの境界を正しく定めることで、これは科学ではなく一種の芸術といえる。
ガイドライン:
- 【ドメインエキスパートの声を聞け】彼らが共通言語を話し、同じ問題に注目しているならばおそらく同じサブドメインにいる。それが境界づけられたコンテキストになる
- 【既存チームや部署の境界に着目せよ】ビジネスが何をドメイン・サブドメインと認識しているかの強力な手がかり。必ずしも一致はしない(部署内で違うことしてたり、他部署とコラボしたり)
- 【境界づけられたコンテキストの「境界」に着目せよ】スコープクリープに気をつけろ。要求が変化する複雑なプロジェクトでは、無慈悲なまでに境界を守る必要がある。大きすぎる境界は何もないのと同じ。よいフェンスはよい隣人を作る
- 【自律可能性が生まれるように設計せよ】足を結ばれた 2 人のランナーは独立した 2 人のランナーよりも速くなることはない。それぞれの境界づけられたコンテキストを独立して開発できるようにしろ
- 【ビジネスフローの摩擦の摩擦を最小化するように設計せよ】複数の境界づけられたコンテキストと関わるビジネスワークフローがそれらによって遅くなっているなら、デザインを損ねてでもワークフローをスムーズにしろ。純粋なデザインよりもビジネスと顧客が重要
変わらない設計などない。いかなるモデルも、ビジネス要件が変わるたびに進化する必要がある。これは受注システムに要件を追加する例として 13 章で話す。
コンテキストマップをつくる
コンテキストマップとは:
- 境界づけられたコンテキスト間の関係を示した全体像。これによって、コンテキスト同士の関係性について技術的詳細に囚われずにコミュニケーションできるようになる
- コンテキストマップは高次に保つ。複雑なシステムにおいては、それぞれのコンテキストの詳細にフォーカスした個別のマップが必要になる
📝 境界づけられたコンテキスト同士が連携するならば、当然それらのコンテキストは、交換可能な共有メッセージのフォーマットについて合意している必要がある。
- フォーマットの決定については主に上流側のコンテキストが主導権を持つ
- 例外: 【レガシーシステムが下流側にある場合など】上流側のコンテキストが、下流側のコンテキストが要求するフォーマットに合わせないといけない。その場合には翻訳コンポーネントが必要
最も重要な境界づけられたコンテキストに着目せよ
ここまでで、いくつか境界づけられたコンテキストを見つけた。 ドメインでの作業をすすめる過程でも、追加で見つかるだろう。 それらは等しく重要だろうか?
どこから着手すべきだろう? → 需要なところから
- 全ての境界づけられたコンテキストを同時に実装したりするのは NG ❌
- 優先順位を付ける必要がある。何が重要かについて、コンセンサスがないこともあるが(各部署は自分こそが最重要だと思っている)
重要とは: ビジネス優位性を生むドメインや売上げを生むドメイン
ドメインの大分類:
- コアドメイン: ビジネスの根幹となるもの
- 補助ドメイン: 業務に必要であるもののビジネスの核ではないもの
- 汎用ドメイン: 他のビジネスでも見られるもの(認証や配送など)。これは安全にアウトソースできる
📝 補助ドメインに見えるものがコアドメインになりうる例: 物販ビジネスにおいて、どの商品も常に出荷可能な状態が保たれていることが顧客満足に決定的に重要な場合、在庫管理ドメインはコアドメインになりうる。
ユビキタス言語をつくる
コードとドメインエキスパートは同じモデルを共有しなければならない
- ドメインエキスパートが「注文」と言ったならば、コードでもそれに対応し、同じように振る舞う「注文」というものを定義する ✅
- 逆に、ドメインエキスパートのメンタルモデルにないもの(注文ファクトリ、注文マネージャ、注文ヘルパーなど)は設計に含めてはならない ❌
- ドメインエキスパートはそれらの意味を理解できないので害にしかならない。もちろんそれらの技術的概念はコードベースの方には出てくるだろうが、それらを設計に入れてはいけない
ユビキタス言語で設計せよ
ユビキタス言語とは: プロジェクトの関係者全員に共有される概念や用語のまとまり。これがビジネスドメインの共通メンタルモデルを定義する。プロジェクトのあらゆる場所で一貫して使われるべき。あらゆる場所とは、コードも含む。
(確か、ドメインモデルを正確に反映するためにコード中のユビキタス言語も日本語で書いている日本のチームの事例があった。言語は Ruby だったと思う)
ユビキタス言語の構築プロセス: 常に WIP。設計とともに進化する
- not ドメインエキスパート起点の一方通行 ❌
- but プロジェクトみんなによるコラボレーション ✅
⚠️ ドメインの「方言」に注意
- ある用語が意味する概念が、ドメインによって微妙に異なる場合がある。イベントストーミング時には特にこれに気をつける必要がある
- 例: 配送部門が言う「注文」は請求部門が言うそれとは微妙に違う。配送部門の関心は在庫つまり数にあるが、請求部門の関心は価格と支払いにある。もし「注文」をコンテキストの明示なしにさまざまな場所で使ってしまうと誤解が生じてしまう
→ 全てのドメインとコンテキストをカバーするユビキタス言語を作るのは無理
ドメイン駆動設計の概念をまとめる
- 【ドメイン】 解こうとする問題に関係する知識領域。ドメインエキスパートが専門とする
- 【ドメインモデル】 特定の問題に関わるドメインの側面を単純化したものの集合。ドメインモデルは解決領域の一部で、ドメインは問題領域。
- 【ユビキタス言語】 ドメインと関連し、チームメンバーとソースコードに共有される概念や語彙の集合
- 【境界づけられたコンテキスト】 明解な境界によって他と区別される、解決領域の子要素。
- 【コンテキストマップ】 境界づけられたコンテキスト間の関係を示した高次の概念図
- 【ドメインイベント】 システム内で起こった事実の記録。必ず過去形で記述され、何らかのコマンド(原文では “activity” と書いてある)をトリガーすることが多い
- 【コマンド】 何らかのプロセスへの開始要求。人やイベントによってトリガーされる。プロセスが成功するとシステムが変化し、一つ以上のドメインイベントとが記録される
まとめ
イベントとプロセス
イベントストーミングによって、ドメイン内の全ての主要なドメインイベントが見えるようになった。 受注プロセスは注文書を郵便で受け取るところから始まり、見積書の作成、新規顧客の登録などのワークフローがあることもわかった。
受注チームが注文を処理し終わると、そのイベントが配送部門の配送プロセスをスタートさせ、請求部門の請求プロセスをスタートさせることも学んだ
他にもプロセスはたくさんあるが、本書では一つに着目する。
サブドメインと境界づけられたコンテキスト
ここまでで、3 つのサブドメインが見つかった(受注、配送、請求)。 これらに対応する 3 つの境界づけられたコンテキストを作り、それぞれの関係を示したコンテキストマップをつくった。
どれが注目すべきコアドメインだろう? どこを自動化すると価値が最大化されるか? → 現時点では受注ドメインからとりかかることにしておく
必要ならば、受注ドメインの仕事を紙に出力することで、他のチームが今まで通りの方法で仕事を続けることもできる。
ユビキタス言語
ここまでで、「注文フォーム」「見積もり」「注文」という言葉を見つけた。 設計を深める過程でもっと見つかるだろう。 ユビキタス言語は、wiki などのライブなドキュメント(印刷した書類は固定化されてしまうが、wiki は更新が前提)でメンテナンスするといい。 チームメンバーの足並みが揃うし、新しいメンバーもキャッチアップしやすい。
次にやることは?
本章によって
- 問題の全体像と、解決策の概略が見えた 💡
- しかし詳細設計やコーディングを始めるにはまだ多くの疑問がある 💭
- ❓ 注文処理ワークフローでは何が行われているのだろう?
- ❓ 入出力はどんな感じ?
- ❓ このワークフローが連携する他のコンテキストはある?
- ❓ 配送チームの「注文」と請求チームのそれはどう違う?
- などなど
次の章では注文確定ワークフローに着目して、これらの問いに答えていく