# 12. Domain Driven Development ________________________________________ エリック・エヴァンスのドメイン駆動設計 10刷 https://www.shoeisha.co.jp/book/detail/9784798126708 実践ドメイン駆動設計 https://www.shoeisha.co.jp/book/detail/9784798131610 TellDontAsk https://martinfowler.com/bliki/TellDontAsk.html GetterEradicator https://martinfowler.com/bliki/GetterEradicator.html ________________________________________ ## 1. What is DDD? ________________________________________ はじめに結論 ```text DDDは、要件定義から実装まですべてに影響を及ぼす開発方針である 1. コアドメインに集中する 2. ドメインエキスパートとプログラマーが共同作業を通じて、モデルを探求すること 3. 境界付けられたコンテキストの内部で、ユビキタス言語を語ること ``` 要約 ```text 1. 要件定義や外部設計など上流工程に、以下のルールを求める - 上流工程で業務概念・業務仕様モデルとその協調(ドメインモデル)を明らかにすることを求める - つまり、顧客(ドメインエキスパート)がコミュニケーション図スケッチを理解できることを求める - サブシステム(境界付けられたコンテキスト)ごとに、明確な用語統一を求める(ユビキタス言語) 2. 最も顧客が重視する領域・投資する価値がある領域(コアドメイン)をはっきりさせて、注力する 3. ユビキタス言語を要件定義からクラス実装まで統一して使用し、ドメインモデルをそのままOOPで表現する - ユビキタス言語は、あくまで境界付けられたコンテキスト内のみで統一されていれば良い - ドメインエキスパートとの会話と実装に登場する単語を統一することで、用語集やExcel設計書などを不要にし、かつ刷新しやすくする - ただし開発初期は整理のためにも必要なことが多い - 結果的に、クラス名・メンバ名にはプログラマー用語はほとんど登場しなくなる - 実装はドメインモデルがOOPとして表現されるため、ドメインモデルが常に最新であることを保証する(保証されないと困る) 4. ビジネスロジック層 = アプリケーション層 + ドメイン層 - ドメイン層が、最重要である - ドメインEntityとER Entityは一致しないし、一致させる必要もない 5. ドメインモデルを洗練する過程で、ドメインが素朴な概念ではなくなる - 素朴な概念よりも鋭い別の角度からの見方になる可能性がある - 業務要件に反映される形で、意図的に概念が取捨選択される 6. その他のルールや手法は、上記を支えるために存在する 7. DDD + DIP = ヘキサゴナル ``` DDDを行うべき基準 ```text 次のいずれかを満たす 1. ハンドラ/コントローラーアクション数が30以上 2. 話すたびにドメインエキスパートが機能を増やしたがる 3. 追加改修・仕様変更改修が続いている ``` ドメイン貧血症チェック(ただのトランザクションスクリプトかどうか) ```text - エンティティまたはアクティブレコードと呼んでいるそれは、メソッドをほとんど持たない単なるデータモデルになってしまっているか? - 実際の処理のすべてを、○○サービスと呼ばれる何かで実装してしまっているか? ``` 用語整理 ```text まず、プログラムの事は一度忘れて、要件定義を思い浮かべる ドメインエキスパート :業務仕様や実態や課題を熟知している人のこと。概ね、顧客のだれか ドメイン :業務領域と実情 サブドメイン :サブ業務領域と実情 コアドメイン :サブドメインの内、最も顧客が重視する領域(投資する価値がある領域) 問題空間 :開発初期のサブドメイン間の関係図。特に未知のサブドメインがありそうorコアドメインが分かっていない時の図 解決空間 :上記に対してサブドメイン・コアドメインを訂正した後に定めた境界付けられたコンテキストのこと 境界付けられたコンテキスト:ドメインに対する実装方針を定める際、最も大きく分割する区分 典型的にはサブシステムや大機能、マイクロサービスの場合の1つのサービス、などビルドグループ単位やリリース単位に対応する場合も少なくない ただし、あくまで言語的・要件的・機能分類的なコンテキスト主導で分割する(そうしないと、ユビキタス言語の境界が適切でなくなる) 例「注文コンテキスト」「在庫コンテキスト」「発送コンテキスト」 ユビキタス言語 :境界付けられたコンテキスト内で定義される共通用語の集合 モデル :模式図 ドメインモデル :ユビキタス言語だけで表現された(≒境界付けられたコンテキスト内の)ドメインの模式図 コミュニケーション図が使われることが多いが、伝われば何でもよい モデリング :モデルを作っていくこと。ユビキタス言語およびドメインの構造や関係性を明らかにしたり、刷新すること DDDの場合ドメインモデルをOOPでそのまま表現するので、要件ヒアリング+モデリングがドメインモデルを導き、≒クラス設計となる ドメインオブジェクト :DDD戦術要素の内、エンティティ、値オブジェクト、集約/集約ルート、ドメインイベントのこと。人によってはDDD戦術要素の総称だったりする エンティティ :ドメインモデル内で、一意に識別する必要があるもの 値オブジェクト :ドメインモデル内で、一意に識別する必要がないもの。ER図で純粋な子テーブルとなるテーブルの一部は、ドメイン層では値オブジェクトだったりする (子テーブルのPK管理はドメイン層の責任ではなく、永続化側の責任にしてよい) ``` DDDモデリングと古典的な設計の違い DDDモデリング |古典的な設計 -----------------------------------------------------------------------|---------------------------------------------------------- 業務概念・ユースケース・シナリオから考える |ERに向かって考える 例1. コラボレーションに関する言語的な関連を持つ単語で構成される |コラボレーション機能はユーザーやパーミッションで権限チェックが必要だ 例2. モデレーターがトピックを凍結できる |パーミッションにはモデレーターを表現できる属性がある 例3. テナントは招待制でユーザー登録を受け付ける |テナントはユーザーを所有している 例4. テナントはアクティブ・非アクティブな場合がある |テナントはアクティブ・非アクティブな場合がある 例5. 利用者は認証が必要だが、テナントがアクティブな時にしか認証できない|ユーザーのテナントが非アクティブな場合、認証は失敗する 業務概念・ユースケース・シナリオがクラス名やメソッド名に対応する |メソッド名等は、実装者にゆだねられる 例1. Contributor.PostTopic() |実装者にゆだねられる 例2. Moderator.FreezeTopic() |実装者にゆだねられる 例3. Tenant.RegisterUser() |実装者にゆだねられる 例4. Tenant.Activate(), Tenant.Deactivate(), Tenant.IsActive |実装者にゆだねられる 例5. AuthenticationService.Authenticate(tenantId, userId, userPassword)|実装者にゆだねられる アプリケーション層とドメイン層が明確に分かれる |UI、DAO、その他という括りになりがち。WinFormではUIとビジネスロジックも分かれない実装になりがち(※) 単一のコンテキスト内には、別のコンテキストのロジックは紛れ込まない |サブシステム・大機能といった括りで整理されるが、用語選定やコンテキストという考え方に無頓着である 例. startDiscussionFor()の外で、予め認証や認可が解決済みだ |startDiscussion()の中で、認証や認可チェックを行ってしまう DIPが推奨され、interfaceも頻繁に定義される |DIPするかどうかは、実装者にゆだねられる ドメインモデルはERに制限されない |ERが実装の中心だ 例1. サロゲートPKはレイヤスーパータイプなどによって隠蔽される |PKがプログラム上で前面に出てくる バリデーションは専任クラス、アサーションは各ドメインオブジェクト |バリデーションもアサーションも深く考察されない TellDontAskに概ね従う |実装者にゆだねられる (※)そもそもVBフォーム系統はかつてUIとビジネスロジックを分けない実装が推奨されていた - コンテキストマップ、OHS/PL・ACL、キャッシュ的な実装 ```text [APIモデルによる説明] ・コンテキスト間で、どちらがどちらのAPIを呼ぶかを図示したもの ・AHS/PLがサーバ側 ・ACLがクライアント側 [プル通知の併用による通信の削減例(キャッシュ的な実装)] ・初回は、クライアントがサーバAPIを呼び出しコンテキスト内の形式に変換して保持する ・2回目以降は、サーバ側が提供する更新履歴APIを見て(プル通知)変更があった場合だけサーバ情報を再取得する ・プル通知ではなく、メッセージング&ローカルキャッシュを採用することもできる。必ずしも最新でなくてよいならこちらがおすすめ ※ ここでいうメッセージングとは、再送機構のある非同期前提のpub/subを指す ``` DDDとOOPとTellDontAskとGetterEradicator ```text 1. OOP設計を行う際、TellDontAskは大筋で正しい 2. Tell(命ぜよ)なので「そこのSよ、Vせよ」となる。主語を省略しない命令形 3. getter, setterは主語がおかしい。撲滅論者はGetterEradicatorと呼ばれる 4. DDDに則すと、命名はコンテキストに従った色鮮やかなユビキタス言語になり強いOOP論者にやや近づく 5. 1~4の結果、tenant.Authenticate(userId, password) という自然な語順のプログラムが増える 6. SVO語順が促進されると、boolメソッドが三単現であったりIs○○であるのも雰囲気が合致する ``` DDDとDBトランザクション ```text 典型的には、アプリケーション層がDBトランザクション制御を受け持つ。ヘキサゴナルでも対応する具象実装側で同様 なお実際のDBセッションなどは何らかの方法でRepositoryが利用できるようにしておく ``` 境界付けられたコンテキスト間の統合(というよりWeb APIの呼び出しからビジネスロジック層に戻ってくるまでの定番設計) ```text ○○Service : 下記2つを実際に利用しドメインレベルのメソッドを提供する ○○Adapter : Httpリクエストを隠蔽する(ここでいうアダプタはGofではなくヘキサゴナルのアダプタを意味している) ○○Translator : 返却されたJSON戻り値をドメインモデルに成型しなおす これらの3つが腐敗防止層相当の役割を果たす ``` UIとアプリケーション層 ```text 1. ドメインモデルを元に、DTO(あるいはMVVMのVM)を成型して渡す 2. ドメインモデルを直接(※ Webならシリアライズして)UIに渡すのはNG 3. ただし、専用のDTOを再定義する必要性を感じない場合は以下が考えられる - 単一の集約ルートで十分ならば、それを派生し何も変更していないクラスをDTOとして扱う - 複数の集約ルートが必要なだけならば、それら必要な参照を持ったドメインペイロードオブジェクト(DPO)を渡す 4. DTOを作成する機構にMediatorを採用する事も考えられる ``` ________________________________________ ## 2. The Building Blocks of a Model-Driven Design ________________________________________ The building blocks ```text Entity : 概念的に同一性が必要なオブジェクト Value object : 概念的には同一性を意識しないオブジェクト。実装時には不変オブジェクト(コンストラクタでのみフィールド値を設定可能)にする事 Service : どうしてもEntityやValue objectの責務としてしっくりこないメソッドを扱う Domain event : IDDDより。「…が発生したとき」「…通知したい」という要件の場合や、あるイベントを別のコンテキストに通知する必要がある場合に使う事がある Module : 名前空間やdllのそれとほぼ同義。作法や考え方は.NETに準ずる Aggregate : 永続化に対するトランザクション整合性に対応するEntityとValue objectをグループとするのがベストプラクティス Aggregate root : Aggregateで基準となるEntity Factories : Aggregateの生成が複雑で、それ自体に生成ロジックを持たせるのも適当でない場合は、各種ファクトリパターンを使ってよい Repositories : 広義のDAOの一種で、永続化関連の処理をすべて請け負う。なおFactoryとRepositoryは混ぜたくなるのは、良くない兆候の1つである ``` Entity or value object? ```text DBの事は1度忘れ、難しく考えずに純粋な概念を追うことで分類する ・概念的な同一性が不要か? ・文脈によって決まる   ・配送システムの住所は単なる値である   ・住所システムの住所はentityである ``` 値オブジェクトの特徴と設計 by IDDD ```text [値オブジェクトの特徴] 1. 計測、定量化、説明。概念だがモノではない 2. 不変。コンストラクタとgetterプロパティ 3. 概念的な統一体。プロパティのどれか1つでも欠けると意味が通らない 4. 交換可能。値オブジェクト型の変数に新しい値オブジェクトを代入しても意味が通る 5. 等価性。==, !=, Equals()をオーバーロードして全プロパティの値比較にする 6. 副作用のない振る舞い。自分に対しても引数の値に対しても副作用はあってはならない [値オブジェクトの設計] 1. ER設計(データ構造・正規化)とドメイン設計が衝突してもドメインモデルを中心に考える 2. 値オブジェクトのコレクションは子テーブルにする。サロゲートPKはレイヤスーパータイプで隠蔽する ``` 集約ルートと整合性 by IDDD ```text 1. 要件のトランザクション整合性と結果整合性を正確に把握する 2. 集約はトランザクション整合性と一致しなければならず、過不足は許されない 3. 集約の半数以上は、単一のエンティティとその他値オブジェクトで構成されるだろう 4. 集約が別の集約の情報を必要とする場合は、アプリケーション層が依存オブジェクトを用意する方式がおすすめ - 集約に対してドメインサービスやリポジトリをコンストラクタDIするより、実際の依存オブジェクトをメソッドのたびに都度引数として直接渡したほうがシンプル - DIロケーターアンチパターンと似たような轍を踏むな。ほしい依存オブジェクトをもらえ 5. 集約間の整合性は、結果整合性によって保証する 6. 結果整合性といえばpub/sub。すなわちメッセージングとドメインイベントだ 7. 要件がトランザクション整合性と結果整合性のどちらかを求めているのか判断できない場合は、 そのデータの整合性を保つのが[誰/どのシステム]の役割なのか考えると良い 8. 集約を実装する時は、デメテルの法則とTellDontAskをある程度意識するとすっきりする ``` 不変条件はどこに記述する? ```text 1. Entity、Value object、Aggregate root自体 2. 内容によってはファクトリ ``` ________________________________________ ## 3. Modeling ________________________________________ モデルを育てる ```text 1. 必要な概念が、会話に出てくるとは限らない。暗黙的な概念がある場合がある - ドメインエキスパートとの会話や設計でぎこちない部分は、何かが隠れている - ドメインエキスパートの話を冷静に聞くと矛盾することが言われることがよくあるが、これも何かが隠れている 2. 制約(不変条件や前提条件や事後条件)を明文化してみる 3. ドメインオブジェクトと責務と実装 - バリデーションや制約や業務手続などの処理は、privateメソッドとして切り出す - バリデーションや制約や業務手続などの判定に、そのオブジェクトの責務とは関係なさそうなデータが必要になる場合、そのオブジェクトの責務ではない - まったく同じ意味・意義を持つバリデーションや制約や業務手続などが別のオブジェクトに登場したら、そのオブジェクト内に記述するのは適切でない - 制約自体または関係性自体が1つのモデル(つまりservice)になるべきである 4. Specification(仕様)オブジェクト - 制約が複合していたり、それ自体がルールであり複雑である場合は「仕様」というモデルを作ってしまうのが良い - 複雑なルールをアプリケーション層に移動するのではなく、ドメイン層のモデルとして仕様オブジェクトにするのがミソ - これと全く同じ処理をRepository=sql化したい場合、repositoryを引数に取るメソッドを定義する(飽くまでspecificationに直接sqlは書かない) ``` しなやかな設計 ```text 意図の明白なインターフェース : 命名規則・正しい名前・明白なシグネチャ。つまりリーダブルコードを実践せよ 副作用のない関数 : 積極的に副作用のない関数としてコーディングできないか考える 表明(Assertion) : 契約による設計(Precondition、Postcondition、Invariant)(本資料説明外) 概念の輪郭 : クラス粒度がドメインオブジェクトや概念と一致しているかを意識する。高凝集低結合 独立したクラス : 低結合を最適化・最大化された状態。Oopの基礎を守れ 閉じた操作 : 例:Matrix.Multiply(m1, m2)、Color.FromArgb(alpha, baseColor) ``` ________________________________________ ## 4. 戦略的設計 ________________________________________ 境界付けられたコンテキスト間の扱い ```text ・境界付けられたコンテキスト:前述を参照のこと ・コンテキストマップ :呼び出し関係や開発チーム体制の現状の図示内容 ・共有カーネル :共有されたソース。双方のコンテキストにも矛盾しない ・顧客/供給者の開発チーム :API提供者側が、必要にAPIを追加する意思がある状態 ・順応者 :API提供者側が、APIを追加する意思はなく、そのAPIモデルをそのまま使う状態 ・腐敗防止層(ACL) :APIを利用する時、ユビキタスに合うように変換を行うレイヤ ・別々の道 :別コンテキストと協調を前提としない独立したシステムとして設計する ・公開ホストサービス(OHS) :API仕様。デザインパターンやWeb APIなど ・公表された言語(OL) :APIに対する、REST、SOAP、XML、JSON ``` 蒸留 ```text ・コアドメイン ・汎用サブドメイン ・ドメインビジョン声明文 ・強調されたコア ・凝集されたメカニズム ・隔離されたコア ・抽象化されたコア ```