ドメイン駆動設計が難しく感じられる理由の一つは各パターンのやり方ばかり勉強してパターンを使用する動機を理解しようとしないからではないか、と思ったので各パターンの背景となる課題も含めてエヴァンス本を要約してみた。
「エヴァンス本」とは和訳版『エリック・エヴァンスのドメイン駆動設計』(翔泳社) のこと。「(補)」と書いたところは @magicant による註釈。
第 1 部: ドメインモデルを機能させる
第 1 部のテーマ: ドメイン駆動設計を達成するための最も重要な要素とは何か
第 1 部のポイント: 言語とモデルとコードは全て相互に関連するものであり、チーム全体でそれらを意識的に設計し続けてゆくこと。
ユビキタス言語
解決したい課題: ドメインエキスパートと開発者が使用する言葉の語彙や意味が異なると、開発者がドメインを深く理解できずモデルを適切に設計できない。書き言葉と話し言葉との差異や、ドキュメントとコードでの言葉の不統一などでも同様の課題が発生し得る。
解決方法: 言語とモデルを不可分のものとして認識し、その相互作用に基づいて言語とモデルの両方を意識的に設計する。開発のあらゆる場面でチーム内の全ての人が同じ言語を使用する。
モデル駆動設計
解決したい課題: ドメインモデルがコードの詳細設計と対応せず、実装内容の妥当性を検証できない。どこのコードが何をするものなのかモデルに基づいて理解できない。モデリングによって得られた洞察あるいはコーディングによって得られた洞察を相互に活かせない。
解決方法: モデルとコードが一致する様に設計し、常にその一致が保たれる様にコードを維持し続ける。モデルとコードの対応関係が理解しやすくなる様な設計ができるプログラミングパラダイムの言語を採用する。
実践的モデラ
解決したい課題: モデリングとコーディングを別々の人が担当すると、モデリング時に得られた有用な知識がコーディング時に活かされない。コードがモデルとは無関係になり、技術的な効率のみを念頭に置いた設計になる。
解決方法: チーム内での役割によらず、モデリングをする人は自らコーディングに多少なりとも時間を費やす様にする。全ての開発者がモデリングに関する議論に参加しドメインエキスパートから知識を得られる様にする。
第 2 部第 4 章: ドメインを隔離する
第 4 章のテーマ: ドメインモデルとそれ以外のものとの関係に関するアーキテクチャ設計
第 4 章のポイント: 「アーキテクチャがドメインに関連するコードを隔離して、凝集度が高いドメインの設計が、システムの他の部分と疎結合できるようにしているなら、そのアーキテクチャはおそらくドメイン駆動設計を支えられるだろう」(エヴァンス本, pp. 76-77)
レイヤ化アーキテクチャ
解決したい課題: ドメインモデルを表現するコードが UI やデータベースを処理するコードと混ざることで、モデルがコード上で把握しにくくなる。また、モデルの単体テストができない。UI 等の変更が意図せずモデルのロジックに影響したりする。
解決方法: ドメインモデルの実装をアプリケーション実装やインフラストラクチャ実装から分離し別のレイヤとして管理する。レイヤの依存関係を一方向に限定することで結合を疎にする。
利口な UI アンチパターン
解決したい課題: 比較的単純なアプリケーションを手早く開発したい。
解決方法: 機能ごと (画面ごと) にインタフェースを分け、インタフェースのコードでビジネスロジックをもまとめて実装する。機能同士をデータベースを通じて結合させる。
デメリット: 製品の保守や機能拡張が困難になる。
理解のカギ: 利口な UI アンチパターンはレイヤ化アーキテクチャの対極にある。つまり、利口な UI を採用することはドメイン駆動設計を諦めることを意味する。
第 2 部第 5 章: ソフトウェアで表現されたモデル
第 5 章のテーマ: モデルをプログラムとして表現するための基礎的なデザインパターンの定義
エンティティ (参照オブジェクト)
定義: 属性によってではなく同一性によって個体が識別されるものの総称。
例 (補): 二人の人の氏名が同じだからといってその二人が同一人物であるとは限らない。ある人の一年前の住所と今の住所が異なるからといってその人が別人であることにはならない。
解決したい課題: ある概念について、別々の個体が同じ属性を持ったり、あるいは一つの個体が持つ属性が変化したりすることがある。その場合、個体の持つ属性全てを単純に比較するだけでは個体が同一のものかどうか正しく判定できない。個体を識別するための適切な手段を確立しておかないと、誤った個体に対して処理が実行されるかもしれない。
解決方法: 個体が同じなのか別々なのか (同一性) を識別する手段をモデルの中に明確化する。同一性の識別をそのクラスの主要な責務とする。個体の属性が変化する可能性があり、変化の前後においても同一の個体であると識別される必要がある (連続性) なら、その点を個体識別方法に反映する。
モデリングのカギ (補): エンティティの各個体がいつ誕生し いつ消滅するのかも意識すること。
値オブジェクト
定義: 同一性によって識別されることがなく、ただ属性や状態を表現するだけのもの。
例 (補): 人の氏名や銀行口座の残高金額は値オブジェクトである。
解決したい課題: エンティティではないものの同一性・連続性を管理すると、無駄に設計が複雑化する。
解決方法: 同一性・連続性の観念をなくし、値の中身に対する操作に集中する。プログラム内では個々の値オブジェクトを不変なデータとして表現する。
モデリングのカギ (補): 個体を識別することがないので、値オブジェクト同士の比較は値の中身を比較するだけになる。
モデリングのカギ (補): 値オブジェクトを不変オブジェクトにするアイディアは、オブジェクトが持つ値の可変性がオブジェクト自身のみによって制御される Java の様な言語を念頭としたものである。オブジェクトの可変性をオブジェクトの外から制御できる言語 (C++ や Rust など) では、値オブジェクトを不変オブジェクトにすることに必ずしも拘らなくてよい。
サービス
解決したい課題: ドメイン内の全ての概念がエンティティと値オブジェクトのどちらかに必ず分類できるわけではない。特定のエンティティや値オブジェクトの概念の一部として実装することが自然ではない操作がある。
解決方法: エンティティや値オブジェクトに属さない操作をサービスとして分離・明確化する。
達成のカギ: サービスにはユビキタス言語に基づいた名前を付けること。サービスの内部に状態を持たせないこと。内容によってはサービスを必ずしもドメイン層ではなくインフラストラクチャ層やアプリケーション層に置くこと。
モジュール (パッケージ)
解決したい課題: フレームワークの規約に引っ張られて、関連するコードが別々のモジュールに分けられ、コードを読んでも概念を理解しにくくなる。モジュールの構成がモデルの全体像と一致しない。
解決方法: クラスやメソッドだけでなくモジュールの構成もリファクタリングする。疎結合と高凝集を意識し、形式だけのモジュール分割を避ける。
第 2 部第 6 章: ドメインオブジェクトのライフサイクル
第 6 章のテーマ: ドメインモデル内に存在するモノのライフサイクルを管理するためのデザインパターン
集約
解決したい課題: 密接に関連する複数のオブジェクトがあるとき、それらの状態を無秩序に変更すると整合性が壊れる可能性がある。かといって一部のオブジェクトを変更するたびに全体にロックをかけるとパフォーマンスを発揮できない。
解決方法: 関連するオブジェクトを集約としてグループ化する。集約の境界外からは集約のルートエンティティしか参照を保持しない様にする。維持すべき整合性 (不変条件) を集約の中で管理し、集約の外からの意図しない変更を防ぐ。
理解のカギ (補): 集約自体は、オブジェクトのライフサイクルとはそれほど密接に関連した概念ではなく、むしろほとんど第 5 章の範疇である。しかし、ファクトリとリポジトリが集約を管理するものなのでその前提知識として集約が第 6 章の最初に出てくることになった。
ファクトリ
解決したい課題: オブジェクトを生成するのに専用のロジックが必要な場合があるが、それをオブジェクト自身に実装すると、オブジェクトの本来の機能に特化した設計ではなくなり、オブジェクトの責務が曖昧になる。一方、オブジェクトの利用者側に生成ロジックを実装すると、集約のカプセル化が守られず、オブジェクトと利用者が密結合になる。
解決方法: 生成処理を専用のファクトリに実装する。集約全体をまとめて生成し不変条件を強制する。
リポジトリ
解決したい課題: ドメイン層やその上のレイヤのどこからでもデータベースに触れる様にすると、集約によるカプセル化を無視して無秩序にオブジェクトが取得・操作される。ドメインのロジックが SQL クエリ等によって直接実装され、エンティティや値オブジェクトが単なるデータ置き場と化す。
解決方法: データベースをコレクションの様なものとして抽象化する。集約のルート以外のオブジェクトをルートを経由せずに取り出すことができない様にカプセル化する。データベースを操作するための具体的な技術 (SQL とか ORM とか) を意識しなくて済む様にカプセル化する。
達成のカギ: リポジトリのインタフェースはその内部のデータベースが持つ機能や性能に影響され得る。逆に、リポジトリの外側がコレクションとしてのリポジトリにどの様な機能を求めるかによってもリポジトリのインタフェースは影響され得る。これらの相互作用を意識して設計する必要がある。
達成のカギ: 複雑なトランザクションを処理する必要がある場合、「リポジトリにオブジェクトを入れる」ための操作は「トランザクションをコミットする」処理とは分けておいた方が良い。
達成のカギ: フレームワークの設計思想がリポジトリの実装に向かない場合、無理やりリポジトリを作るよりはフレームワークの標準的使用法を尊重する方が良い。(そのフレームワークの使用が避けられない限りは)
第 3 部第 8 章: ブレイクスルー
第 8 章のポイント: 継続的なリファクタリングを通じてモデルとコードを改良し続けるうちに、それまでの設計とは大きく異なるがモデルとしては遥かにより「正しい」設計がひらめくことがある。
第 3 部第 9 章: 暗黙的な概念を明示的にする
第 9 章のテーマ: ドメインの中に潜む概念をどうやって発見し、モデル化するか
第 9 章前半のポイント: 言葉に耳を傾けたり、ぎこちなさや矛盾について熟考したり、文献を調べたりすることによって、未知の概念の存在を発見できることがある。
第 9 章後半のポイント: ドメインにおけるビジネス上の制約やプロセスはモデルの中でも重要な概念なので、詳細な実装コードの中に埋没させるのではなくクラスやメソッドとして明示的に表現すべきである。サービスはプロセスを明示的にカプセル化する手法の一つである。仕様は一種の制約を明示的にカプセル化する手法の一つである。
仕様
解決したい課題: オブジェクトがある条件を満たすかどうか判定する (検証) ことと、ある条件を満たす既存のオブジェクトを探し出す (選択) ことと、ある条件を満たす新しいオブジェクトを作り出す (構築) ことは、その「ある条件」が同じ事柄を指す場合でも、実現手段が異なるためにバラバラに実装されがちである。そのため、その条件がドメインにおいて重要な役割を果たす概念であっても、詳細な実装コードの中に埋没し、その存在が不明瞭になる。
解決方法: 条件を一つの値オブジェクトとして明示的に表現する。検証と選択の具体的実装をそのオブジェクト内に集めることで、この条件が概念としては同一のものであることを体現する。
第 3 部第 10 章: しなやかな設計
第 10 章のテーマ: コードを利用する開発者にとって及びコードを変更しようとする開発者にとってコードを理解しやすくするための設計パターン
意図の明白なインタフェース
解決したい課題: あるオブジェクトを利用するためにそのオブジェクトの中身をよく理解しなければならない場合、利用される側の設計を理解することに脳のリソースが消費されて、利用する側の設計を熟考することに集中できない。コードベース内に実装された機能の用途が不明瞭だと、偶然内容が類似したコードが本来とは異なる目的に転用されかねない。
解決方法: クラスやメソッドの名前には、(その機能を達成する手段ではなく) その機能や目的を表現する名前を付ける。名前をユビキタス言語に含まれる語彙に一致させる。これにより、オブジェクトの中身の実装を細かく理解しなくてもオブジェクトの利用方法を理解できる。
達成のカギ (補): ユビキタス言語が日本語である場合、必然的にクラスやメソッドも日本語で命名することになる。
副作用のない関数
解決したい課題: メソッドがどの様な副作用を起こすのかがインタフェース上で明白でない場合、メソッドの実装内容やその依存先の内容を理解してそれらが意図しない副作用を起こすことがないかいちいち吟味しなくてはならない。
解決方法: 状態を変化させる副作用のある操作と副作用のない操作を分離する。関数が不変な値オブジェクトに置いてある場合、副作用がないことが分かりやすくなる。
表明
解決したい課題: 副作用のある操作を理解するために、実装コードを良く読み込んだり実際にコードを実行させて結果を確かめてみたりしなければならないことがある。
解決方法: クラス・集約の不変条件や操作の事後条件を条件式の形で表明する。表明をコーディングできない場合は自動単体テストを書く。
概念の輪郭
解決したい課題: いろいろな要素を一枚岩の中に押し込めるとインタフェースを通じて個々の概念を理解することが難しい。一方でクラスやメソッドを分割しすぎると本来存在した概念が見えなくなる。
解決方法: ドメイン内で根底にある概念への理解に基づいて、設計を凝集させる。
達成のカギ (補): 概念の輪郭は、説明を聞いただけで理解するのは難しいが、エヴァンス本 p. 270 にある以下の一節がヒントになる:
このように簡単に拡張できたのは、開発者が変更を予期したからではない。また、設計の用途を非常に広くして、考えられるどんな変更にも対応できるようにしたからでもない。以前のリファクタリングによって、設計がドメインの根底にある概念と揃えられたからである。
コードを共通化したり分割したりするにあたって、「処理内容が同じだから」の様な表層的側面ではなく、ドメイン内の概念に沿って設計するのが重要なのだ。
独立したクラス
解決したい課題: オブジェクトに依存関係が多いと、そのオブジェクトを理解するための精神的負荷が増す。
解決方法: オブジェクトからできるだけ他の概念を取り除き、単独で理解できる様にする。
閉じた操作
解決したい課題: たくさんの概念に依存するインタフェースの定義は理解しにくい。
解決方法: 可能ならば、操作の入力と出力 (引数と戻り値) が同じ型になる様にする。
第 3 部第 11 章: アナリシスパターンを適用する
第 11 章のポイント: ドメインの分野によっては文献や他のプロジェクトでの経験から得た知識をモデリングに活かせることがある。
第 3 部第 12 章: デザインパターンをモデルに関係づける
第 12 章のテーマ: 純粋に技術的な課題を解決する手段でしかないと見なされがちなデザインパターンをどうやってドメインのモデルと結び付けるか
ストラテジー (ポリシー)
解決したい課題: プロセス (何らかの物事のやり方) を選択・交換できる様な仕組みをモデリングしたい。
解決方法: プロセスを抽象化して交換可能にする。
理解のカギ: ストラテジー (ポリシー) パターンを「アルゴリズムを選択するためのもの」と見なすと単なる技術的な問題を解決するパターンに過ぎないが、「ドメインにおけるプロセスの選択を表現するもの」と見なすとそれはドメインのモデルの一部となる。
コンポジット
解決したい課題: 小さな部品からより大きな部品を組み立てる (あるモノが同種のモノを再帰的に含む) ことができる様な概念をモデリングしたい。
解決方法: 全ての構成要素に共通する抽象型を定義し、全体に対しても個々の部品に対しても同じインタフェースで操作できる様にする。
第 4 部第 14 章: モデルの整合性を維持する
第 14 章のテーマ: 複数に分かれたモデルをどの様に管理するか
境界づけられたコンテキスト
解決したい課題: 複数のモデルが同じコードベース内に混ざると、ロジックに矛盾が生じ、開発者間のコミュニケーションは混乱する。
解決方法: モデルがどの範囲で利用可能なのかをコンテキストとして明確化する。それぞれのコンテキストの中でモデルを一貫性のあるものに保つ。
達成のカギ: コンテキストの境界は、ユビキタス言語の方言の使用範囲や開発チームの分け方やコードベースの構造やデータベースのスキーマなどとうまく連動する様に分けられるとよい。
達成のカギ: コンテキスト内のモデルの一貫性の崩壊に気付くヒントが二つある:
- 重複した概念: 同じ概念が複数の異なる言葉で呼ばれる
- 偽同族語: 同じ言葉が複数の異なる概念を指す。人によって言葉の意味が異なる
モジュールとの比較: モジュールは一つのモデルの中で概念を整理するためのもの。コンテキストは複数のモデルがあるときにそれらの境界を明確化するためのもの。ただし、コンテキストが別々ならば普通は結果としてモジュールも分かれる。
継続的な統合
解決したい課題: 複数の開発者が同じコンテキストで作業すると、開発者ごとにモデルに差が生まれやすい。かといって開発者ごとに細かくコンテキストを分けると、それぞれのモデルが属人化し、一貫した機能をチームで開発してゆくことができない。
解決方法: 一つのコンテキストの中で……
- モデルの概念に対する認識 (特に、ユビキタス言語の語彙と意味) のずれがないか、開発者同士で頻繁にコミュニケーションを取って確認する。
- 各開発者の作業成果物を頻繁にマージして、コードがバラバラに進化するのを防ぐ。
補: 継続的な統合 (Continuous Integration; CI) の話をすると、マージしたら自動でテストを実行することに注目が集まりがちだが、CI の本質はそこではなく、継続的に統合をすることそれ自体が本質である。ドメイン駆動設計では特に、モデルをプログラミング言語によって表現したものであるコードベースを頻繁に統合することで、モデルに対する認識や理解を開発者間で統一することを目指す。
コンテキストマップ
解決したい課題: コンテキスト同士の境界がどこにあるのかを開発者たちが認識しておかないと、異なるコンテキストのコードが混ざり合って混乱が生じる。
解決方法: コンテキストにそれぞれ名前を付けてユビキタス言語の一部とする。コンテキスト同士の関連 (接点) を図示するなどして開発者全員が理解できる様にする。
達成のカギ: コンテキストマップは開発プロジェクトの現状を示すものとして作ること。現状を良く把握した上で、コンテキスト同士の接点で必要となる変換をどうやるか戦略を立てること。
共有カーネル
解決したい課題: 複数のチームが密接に関連する機能を開発する場合、チームごとにコンテキストを完全に分離してバラバラに開発すると、それらの機能を連結するために必要な変換や調整のコストが高くつく。
解決方法: ドメインモデルの一部を複数のコンテキストで共有する。それに対応するコードやデータベース設計等も共有する。共有された部分を変更するときはチーム同士で相談して合意する。
顧客/供給者の開発チーム
前提: 二つのサブシステムの依存関係が一方向である場合、つまり、上流のサブシステムが機能や情報を提供し下流のサブシステムがそれを利用する関係の場合
解決したい課題: 上流の変更に対して下流の開発チームに拒否権があったり変更のための手続きが面倒だったりすると上流の開発チームは柔軟に動けない。あるいは、上流チームが下流チームに対して気を遣って自分たちの仕事に集中できないこともある。一方、下流チームは上流チームが決めた機能の内容や開発スケジュールに振り回される。
解決方法: 上流を供給者、下流を顧客としてチームの関係を確立させる。下流チームは下流が上流に対して必要とする機能を顧客として要求し、上流チームはそれを計画に反映する。サブシステム間のインタフェースを検証する自動化された受入テストを共同で開発し、上流の継続的な統合に含めて実行する。
達成のカギ: チーム間で交渉をして両者が合意できる計画を立てること。
順応者
前提 1: 二つのチームの依存関係が一方向であり、しかも、下流からの要求に応じる動機が上流チームにない場合。(顧客が多すぎて対応しきれないとか、部署が異なるので積極的に協力してくれないとか)
解決したい課題: 下流チームが上流チームに要望を出しても無視されたり計画通りに実装されなかったりする。必要な機能を上流チームが用意してくれる見込みの元で下流チームが立てた計画は破綻する。
前提 2: 上流の設計が比較的マシであり、下流にとって互換性がある場合
解決方法: 上流のモデルを下流でも受け入れて使用する。上流と下流のコンテキストが共通化されることで統合が楽になる。
腐敗防止層
解決したい課題: 粗悪または理解困難なモデル設計を持つ既存のシステムと自分たちのシステムを結合しなければならない場合、自分たちのシステム内にまで既存のモデルを持ち込むと自分たちの方まで設計が悪くなる。
解決方法: 既存のシステムと自分たちのシステムとがやり取りする部分でモデルの変換をすることで、自分たちのシステム内で独自のモデルを構築し、既存のシステム側のことは気にしなくて済む様にする。
別々の道
解決したい課題: 複数の機能を開発する必要があるが機能同士の相互作用や関連が少ない場合、それらを同じコンテキストに入れてもモデルが複雑化するだけであまりメリットがない。
解決方法: コンテキストを分け、機能を別々に開発する。コンテキスト間の依存も避ける。
公開ホストサービス
解決したい課題: あるサブシステムをたくさんの他のサブシステムと結合する必要がある場合、それぞれのサブシステムに対して個別に変換サービスを実装するのは負担が大きい。
解決方法: 複数のサブシステムから使用される機能のインタフェースを汎用化し、どのサブシステムからも同じインタフェースで使用できる様にする。
達成のカギ: インタフェース設計を綺麗に保つのにも労力がかかるので、一つのサブシステムからしか使用されない機能までインタフェースを汎用化する必要はない。
公表された言語
解決したい課題: 複数のコンテキスト間で情報をやり取りするとき、片方のモデルからもう片方のモデルへ直接変換させると、異なるコンテキストにあるモデル同士が強く結合し、独立して改良してゆくことが難しくなる。
解決方法: モデル間でデータを交換するための共通の言語 (プロトコル) を作ってドキュメント化する。
第 4 部第 15 章: 蒸留
第 15 章のテーマ: モデルの中で特に重要で価値のある部分を見付け出し、集中して開発できる様にする手法。
コアドメイン
解決したい課題: 開発に費やせる時間は有限であり、システム全体を満遍なく良い設計にすることは難しい。設計を洗練させるべき部分に注力しないと、顧客や利用者が求める価値を提供し続けることができなくなる。
解決方法: モデル内の重要な部分がどこなのかよく分かる様に区別する。優秀な開発者にその部分を担当させる。
達成のカギ: 技術的な興味ではなくビジネスの観点でドメインの重要な部分を選ぶこと。
達成のカギ (補): 優秀な開発者はメタプログラミングの様な専門的な技術を駆使することや流行りのフレームワークを触ることなどに喜びを見出しがちだが、それがドメインモデルをより深くしなやかな設計に進化させることに繋がらなければあまり意味がない。
達成のカギ: コアドメインがどこであるかの識別も、イテレーションを通じて進化させること。
汎用サブドメイン
解決したい課題: ドメインモデルの中にはそのドメインに固有のものではない事柄が含まれることがある。タイムゾーンの変換やユーザーアカウントの管理・認証は多くのウェブサービスで必要とされる機能だが、普通はコアドメインにはならない。その様な (それなりに複雑な) 機能がシステム内に存在するとき、そこがコアドメインの一部であると誤解される恐れがある。
解決方法: 汎用的な機能をコアドメインとは別のモジュールに分離して、優秀な開発者があまり重要でない機能に気を取られるのを避ける。既成のライブラリーを使ったり、既存の設計・モデルを流用したりする選択肢もある。
ドメインビジョン声明文
解決したい課題: ドメインモデルが事業とどの様に結び付くのかが分からないと、経営者と開発者が同じ目標を共有することができず、意思決定がちぐはぐになる。
解決方法: コアドメインが何であり、それがどの様な価値をもたらすのかを、文書化する。
強調されたコア
解決したい課題: システムの中でどの部分がコアドメインなのかについての詳細な認識は、人によって (あるいは同じ人でも日によって) ずれが生じやすい。コードの構造 (モジュール構成) を整理してコアドメインの範囲を示すこともできるが、それをするには先に関係者の認識を一致させる必要がある。
解決方法: (ドメインビジョン声明文よりは具体的な) コアドメインの範囲を示すドキュメントを作る。
解決方法: コアドメインに入るものに印 (フラグ) を付ける。
達成のカギ: コアドメインの範囲を分かりやすくすることが目的なので、やり方の細かい部分は柔軟に変更してよい。
凝集されたメカニズム
解決したい課題: 問題を解くための手段が複雑な場合、「どうやって問題を解くか」が設計の大部分を占め、その動機である「どんな問題を解くか」が目立たなくなる。
解決方法: 問題を解くための手段を軽量フレームワークとして分離する。
汎用サブドメインとの比較: 汎用サブドメインは (コアドメインほど重要ではないが) ドメインの一部である。一方、凝集されたメカニズムはドメインとは独立した単なるアルゴリズムである。ただし、モデルの蒸留が不十分なうちは二つを明確に区別しづらいことも多い。
隔離されたコア
解決したい課題: コアドメインの要素がコアドメイン外の要素とたくさん結合すると、コアドメインを柔軟にリファクタリングすることが難しくなる。
解決方法: コアドメインを専用のモジュールに括り出す。モジュールを分離することでコアドメイン外の要素との結合を疎にする。
抽象化されたコア
解決したい課題: コアドメインが多数のサブドメインモジュールで構成されるとき、モジュール同士でいろいろな相互作用があると (密結合)、モジュールごとの独立性が薄れ、モジュール分割のメリットがなくなる。
前提: コアドメインにある様々な要素が共通の特徴を有し、それによって要素間の相互作用を表現できる場合。
解決方法: コアドメインにある要素に共通する特徴を抽象インタフェースとして括り出し、それを独立したモジュールに置く。インタフェースの実装はサブドメインごとの別モジュールに分ける。実装が抽象インタフェースだけに依存する様になれば、実装同士の相互作用がなくなるのでモデルが理解しやすくなる。
第 4 部第 16 章: 大規模な構造
第 16 章のポイント: システム全体に亘る包括的な設計原則・パターンを定め、各モジュールに浸透させる。これにより、モジュールごとに独自に構築された設計様式をいちいち把握する必要がなくなり、各モジュールの位置付けが理解しやすくなる。
進化する秩序
解決したい課題: 指針となる設計ルールを開発開始前に厳格に定めた場合、妥当性に関係なく無理やりルールを当てはめようとして設計がぎこちなくなったり、または各開発者が独断でルールを無視する様になって混沌と化したりする。
解決方法: 大規模な構造はアプリケーションと共に進化させる。開発の途中でより良い構造に作り直すことも検討する。
達成のカギ: 大規模な構造によってむしろ設計がぎこちなくなる位なら構造がない方がマシである。システムにうまく整合する構造が見付かったときのみ導入した方が良い。
システムのメタファ
解決したい課題: システムの全体設計はしばしば抽象的で把握しにくい。そのため複数の開発者やユーザーの間でシステムに対して同じ見方を共有することが難しい。
解決方法: システムを表す具体的なメタファ (譬喩) があり、それによって開発者たちのドメインに対する見方を同じ方向に導くことができると見込まれるならば、それを大規模な構造として採用する。
達成のカギ: 場合によっては、メタファがむしろドメインに対する見方を過度に狭め、柔軟な設計を阻むかもしれない。メタファが適切なものかよく吟味すること。適切なメタファが見付かるプロジェクトはなかなかない。
責務のレイヤ
解決したい課題: ドメインの中にあるたくさんのオブジェクトやモジュールを整理したいが、その場しのぎでレイヤに分けても、「なぜそれをそのレイヤに置いたのか」を説明できない。
解決方法: ドメイン層の中に、概念の依存関係と責務の分担に基づいたレイヤ構造を作る。ドメインのオブジェクト・集約・モジュールが一つのレイヤに収まる様にする。
達成のカギ: 単に技術的な観点でレイヤを分けるのではなく、ドメインにおけるビジネス上の意図を構造に反映させる。
知識レベル
解決したい課題: 顧客やユースケースによってオブジェクトの働きや関係の制約が異なることがあるが、個別にソフトウェアをカスタマイズするのは現実的でない。一つのモデルで全ての場合に対応できる様にしようとするとあまりにも汎用的で複雑すぎるものになる。
解決方法: モデルを知識レベル (メタレベル) の部分と業務レベル (ベースレベル) の部分に分ける。知識レベルでは具体的な動作や制約の内容を定義する。業務レベルでは知識レベルで定義された要素を使って個々のユーザーがオブジェクトをカスタマイズする仕組みを提供する。
レイヤとの比較: レイヤ分けは依存関係が一方向なのに対し、レベル分けは相互に依存する点が異なる。
達成のカギ: 凝りすぎると、ソフトウェアが汎用的になりすぎたり設定方法が複雑になりすぎたりして、ユーザーにとってむしろ分かりにくくなる。
着脱可能コンポーネントのフレームワーク
解決したい課題: 同一の概念に関係する多様なアプリケーションを相互運用する必要があるとき、それらのアプリケーションが独自に設計されると、共通の概念に関する設計がバラバラになるので多くの変換が必要になる。
解決方法: 抽象化されたコアを蒸留し、インタフェースの実装を自由に置換できる様なフレームワークを作る。各アプリケーションを共通フレームワークに基づいて実装する。
達成のカギ: どの様な部分を共通フレームワーク化すると良いのかは、最初からは分からない。フレームワーク化するとコアの設計を後で変えることが難しくなるので、十分に洗練されたコアができてから始めるべき。