Share
Embed
Transcript
52 slides
-
Learning Domain-Driven Design 輪読会 #7 株式会社Showcase Gig 中島 清貴
(@ahyataro) #lddd_rindoku Chapter 6. Tackling Complex Business Logic -
OverView Chapter 6. Tackling Complex Business Logic • History /
歴史 • Domain Model / ドメインモデル • Conclusion / 結論 • Exercises / エクササイズ -
History / 歴史 『Patterns of Enterprise Application Architecture』 著:
Martin Fowler ドメインモデルパターンが最初に紹介された著書。Fowlerは このパターンに関して「Eric Evansは現在、ドメインモデルの 構築に関する本を書いている」と言って締めくくった -
History / 歴史 『Domain Driven Design: Tackling Complexity in
the Heart of Software』 著: Eric Evans コードをビジネスドメインの基礎となるモデル(集約、値オブ ジェクト、リポジトリなど)に厳密に関連づけることを目的とし た一連のパターンを提示している。 -
History / 歴史 • Evansが導入したパターンは戦術的ドメイン駆動設計と呼ばれることがよくある • ビジネスロジックを実装するためにこれらのパターンを必ず使わなければならな いという考え方をなくすために、LDDD本の著者はFowlerの元の用語を使うこと を好んでいる •
パターンはドメインモデルであり、集約や値オブジェクトは構成要素であると考 えている -
• 顧客がサポートチケットを開く • 顧客とサポートエージェントの両方がメッセージを追加し、全てのやりとりがサポートチケットによって追跡 される • 各チケットには低、中、高、緊急の優先度がある • エージェントはチケットの優先度に基づいて設定された時間制限(SLA)内にソリューションを提供する必要
がある • エージェントがSLA内で応答しない場合、顧客はチケットをエージェントのマネージャーにエスカレーション できる • エスカレーションによりエージェントの応答時間制限が33%短縮される • エージェントが応答時間制限の50%以内にエスカレーションされたチケットを開かなかった場合、そのチ ケットは自動的に別のエージェントに再割り当てされる • 顧客が7日以内にエージェントの質問に返信しない場合、チケットは自動的にクローズされる • エスカレーションされたチケットは顧客またはエージェントのマネージャーのみが自動またはエージェントに よってクローズすることはできない • 顧客は過去7日間に閉じられたチケットのみ再開できる -
Implementation / 実装 • ドメインモデルは振る舞いとデータの両方を組み込んだドメインのオブジェクトモ デルである • DDDの戦略的なパターン(集約、値オブジェクト、ドメインイベント、ドメインサービ ス)はドメインモデルの構成要素である •
これらのパターンは全てビジネスロジックを第一に考えるという共通のテーマが ある -
Building Blocks / 構成要素 DDDにおける主な構成要素として以下のような戦術的パターンがある • Value objects / 値オブジェクト
• Aggregates / 集約 • Domain service / ドメインサービス -
Value objects / 値オブジェクト • 右のColorクラスの例では ◦ いずれかのフィールドの値を変更すると新しい色になる ◦
2つの色が同じ値を持つことはできない ◦ 同じ色の2つのインスタンスは同じ値を持つ必要があり、色 を識別するための明示的な識別子は必要ない • 図6-1のようにcolor-idは冗長であるだけでなくバグの入り口を生ん でしまう ◦ 赤、緑、青の同じ値を持つ2つの行を作成できるが、color-id の値を比較しても、これが同じ色であることを確認できない 値の構成によって識別できるオブジェクト -
Value objects / 値オブジェクト • ビジネスドメインの概念を表現するために、言語の標準ライブラリ のプリミティブデータ型のみに依存することはPremitive Obsession Code Smell
として知られている • 例えば右のPersonクラスでは、 ◦ ほとんどの値がString型であり、landlinePhoneは固定電話 番号でなければならないなどの制約がある ◦ ユーザが常に正しい値を入力するわけではないのでクラス はすべての入力フィールドを検証する必要がある • このアプローチの設計上のリスク ◦ バリデーションロジックが重複する傾向がある ◦ バリデーションロジックの呼び出しを共有することが難しい • 以上の問題を解決するために値オブジェクトが活用できる -
Value objects / 値オブジェクト • 明確さが増していることに注目 ◦ 例えば、country変数では完全な国名ではなく国コードを保持して いるという意図を伝えるために、それを精巧にcountryCodeと命
名をする必要がない ◦ 値オブジェクトを使うことで変数名が短くても意図を明確にできる • バリデーションロジックが値オブジェクト自体に存在するため、割り当ての 前に値を検証する必要がない ◦ 値オブジェクトの動作は単なるバリデーションだけではない • 値オブジェクトはビジネスロジックを一元化するときに最も輝く ◦ まとまりのあるロジックは一か所に実装されテストが簡単 • 最も重要なのは、コードがユビキタス言語を話すようにバリデーションオ ブジェクトがビジネスドメインの概念を表現することである -
Value objects / 値オブジェクト • 値オブジェクトのフィールドのいずれかを変更すると異なる値に なるため、値オブジェクトは不変オブジェクトとして実装される • MixWithメソッドを使用するこのケースのように、実行されたアク ションによって新しい値が生成されると、元のインスタンスは変
更されず新しいインスタンスが返される • 値オブジェクトの等価性は、idフィールドや参照ではなく値に基 づいているため、値同士を比較する必要がある -
Value objects / 値オブジェクト 値オブジェクトはいつ使えるか • 「いつでもいいよ」 • 値オブジェクトはコードをより表現豊かにし、ばらばらになりがちなビジネスロジックを カプセル化するだけでなく、コードの安全性も高まる
• 値オブジェクトは不変であるため、値オブジェクトの動作には副作用がなくスレッド セーフである • 値オブジェクトは次に説明するエンティティのプロパティに適用する ◦ 前述のPersonクラスの例では、ID、名前、電話番号、電子メールなどに値オブ ジェクトを使用していた -
Entities / エンティティ nameという1つのフィールドのみの場合、同じ名前の人が存在する 可能性があり、名前だけでは人物を適切に識別できない 明示的な識別子で区別するオブジェクト 識別子を導入する • PersonIdは値オブジェクトであり、ビジネスドメインのニーズに合った任意のデータ
型(GUID、数値、文字列、社会保障番号など)を使用できる • 識別子はエンティティの各インスタンスに対して一意である必要がある • 識別子はエンティティのライフサイクルを通して不変のままである必要がある -
Entities / エンティティ • 値オブジェクトとは異なりエンティティは不変ではない • 値オブジェクトがエンティティのプロパティを記述する • ドメインモデルの構成要素にエンティティを含めていない ◦
含めていない理由は、エンティティは独立して実装するのではなく、集約 パターンのコンテクストのみで実装するから -
Aggregates / 集約 • データの整合性を取るために、集約とその外部スコープの間に明確な境界を引く • 集約のロジックは、受け付けたすべての変更を検証し、その変更がビジネスルールと矛盾しな いようにしなければならない •
実装の観点から見ると、整合性は集約のビジネスロジックのみがその状態を変更できるように することで強制できる • 集約の外部にあるすべてのプロセスまたはオブジェクトは集約の状態の読み取りのみが許可 される • その状態は集約のパブリックなメソッドを実行することによってのみ変更できる 整合性の強制 -
Aggregates / 集約 • 集約のパブリックインターフェースとして公開される状態変更メ ソッドはコマンドと呼ばれることがよくある • コマンドの実装方法 ◦ 集約オブジェクトのプレーンなパブリックメソッドとして
実装する ◦ コマンドの実行に必要なすべての入力をカプセル化す るParameter Object として実装する ◦ どちらを選ぶかは好みの問題 • 集約のパブリックインターフェースは入力を検証し、関連する ビジネスルールと不変条件を強制する • この厳密な境界により、集約に関連する全てのビジネスロジッ クが一か所に実装されることが保証される -
Aggregates / 集約 • 集約に対する操作を実行するアプリケーションレイ ヤーは非常に単純になる • 集約の現在の状態を読み込み、必要なアクションを実 行、変更された状態を保持し、操作の結果を呼び出し 元に返すだけになる
• 11行目の同時実行チェックに注意 ◦ 複数のプロセスが同じ集約を同時に更新して いる場合は、後者のトランザクションが最初のト ランザクションの変更を盲目的に上書きしない ようにする必要がある ◦ このような場合は2番目のプロセスは、状態が 古くなっていることを通知し、操作を再試行する 必要がある -
Aggregates / 集約 • 集約の永続化に使用されるデータベースは同時実行管理をサポートす る必要がある • 最も単純な形式では、集約は更新のたびにインクリメントされるバージョ ンフィールドを保持する方法がある •
データベースに変更をコミットするときは上書きされるバージョンが最初 に読み取られたバージョンと一致することを確認する • このSQLでは、集約の状態に加えられた変更(2行目)を適用し、その バージョンカウンター(3行目)を増やすが、現行バージョンが集約の状態 に変更を適用する前に読み取られたバージョン(4行目)と等しい場合に 限る • 同時実行管理はリレーショナルデータベース以外でも実装できる • ドキュメントデータベースは集約の処理に適している • ただし同時実行管理をデータベースがサポートしていることを確認する ことが重要 -
Aggregates / 集約 • 集約の状態は独自のビジネスロジックによってのみ変更できるので、集約はトランザクション 境界としても機能する • 集約の状態に対するすべての変更は、1つのアトミック操作としてトランザクションでコミットす る必要がある •
複数集約のトランザクション実行は想定できない ◦ 1トランザクション1集約のみ • 集約の境界は慎重に設計し、設計がビジネスドメインの不変条件とルールに対応していること を確認しなければならない • 複数の集約で変更をコミットする必要があることは、間違った境界であることを示す トランザクション境界 -
Aggregates / 集約 • エンティティは独立したパターンとして使用するのではなく、集約の一部としてのみ使用 する • 複数のオブジェクトがトランザクション境界を共有する必要があるビジネスシナリオがあ る ◦
例えば両方を同時に変更できる場合や、あるオブジェクトのビジネスルールが 別のオブジェクトの状態に依存している場合など • 複数のオブジェクトへの変更を1つのトランザクションで実行するために、集約パターン でエンティティの階層を表現し、すべてのトランザクションの整合性を実現する • 階層にはエンティティと値オブジェクトが含まれ、ドメインのビジネスロジックによってバ インドされている場合はそれらは同じ集約に属す エンティティの階層 -
Aggregates / 集約 • このコードサンプルでは、集約の境界に属する複数のエンティティ にまたがるビジネスルールを示している • 「エージェントが応答時間制限の50%以内にエスカレーションされた チケットを開かなかった場合、そのチケットは自動的に別のエージェ
ントに再割り当てされる」というロジックを実装している • 集約は、すべての条件が強く整合性のあるデータであることを チェックされることを保証し、すべての変更が1つのトランザクション として実行されるようにすることで、チェック完了後は変更されない ようにする -
Aggregates / 集約 • 集約に含まれるすべてのオブジェクトは同じトランザクション境界を共有するため、集約が大きくなりすぎるとパ フォーマンスやスケーラビリティの問題が発生する可能性がある • データの整合性は集約の境界を設計するための便利な指針となる
• 集約のビジネスロジックが強く整合性を保つために必要な情報のみが集約の一部でなければならない • 最終的に整合性が取れればいいような情報は集約の境界の外側に存在させるべき 他の集約への参照 -
Aggregates / 集約 • 集約をできるだけ小さく保ち、強い整合性を保つ必 要があるオブジェクトのみ含めるようにすべき • この例ではTicket集約はMessageのコレクションを持 つ
• 一方でCustomerやProduct、Agentは集約に属さな いためIDの参照を持つ • IDによって外部集約を参照する理由は、これらのオ ブジェクトが集約に属していないことを明示的にする ため -
Aggregates / 集約 • 集約はエンティティの階層を表現するため、エンティティの パブリックインターフェースとして指定する必要があるのは そのうち1つだけで、それを集約のルートという • この例では特定のメッセージを既読できるコマンドを公開し
ている • この操作はMessageエンティティを変更するが、集約ルート Ticketを介してのみアクセスできる • 外部の世界が集約と通信できるもう一つの仕組みとしてドメ インイベントがある 集約のルート -
Aggregates / 集約 • ドメインイベントはビジネスドメインで発生した重要なイベントを表すメッセージ • 例: ◦ チケットがアサインされたこと
◦ チケットがエスカレーションされたこと ◦ メッセージを受け取ったこと etc.. • ドメインイベントの目標は、ビジネスドメインで何が発生したことを記述し、イベントに関連する必要なデータを 提供すること ドメインイベント -
Aggregates / 集約 • ドメインイベントは集約のパブリックインターフェースの一部 であり、ドメインイベントを発行する • 他のプロセスや集約、外部システムは、ドメインイベントをサ ブスクライブして独自のロジックを実行できる
• このコードでは、新しいドメインイベントがインスタンス化され (12行目)、チケットのドメインイベントのコレクションに追加さ れる(13行目) -
Domain Service / ドメインサービス • どの集約または値オブジェクトにも属さないビジネスロジック、ま たは複数の集約に関連しているように見えるものに遭遇すること がある • このような場合ドメインサービスが使える
• ドメインサービスはビジネスロジックを実装するステートレスオブ ジェクトであり、システムのさまざなまコンポーネントへの呼び出し を調整し、計算または分析を実行する • チケット集約の例では、割り当てられたエージェントには顧客にソ リューションを提案する期間が限られている • 応答時間枠計算ロジックには複数のソースからの情報が必要 • この場合ドメインサービスとして実装することが理想 -
Domain Service / ドメインサービス • ドメインサービスを利用すると複数の集約の作業を簡単にできる • ただし、1つのデータベーストランザクションで1つだけ変更するという集約パ ターンの制限を忘れないこと •
ドメインサービスはこの制限を回避する抜け穴ではない • ドメインサービスは複数の集約のデータを読み取る必要がある計算ロジック の実装に役立つ -
Managing Complexity / 複雑さの管理 • ビジネスマネジメントの第一人者であるEliyahu M. Goldratt氏は、 著書「The
Choice」の中でシステムの複雑さを簡潔かつ強力に定義している • Goldratt氏によると、システムの複雑さを議論するときに、システムの振る舞 いを制御および予測することの難しさを評価することに関心を持つとのこと • これらの2つの側面はシステムの自由度に反映される • 例えば右のコードでは一見ClassBの方が複雑そうに見えるが自由度は低 い • 振る舞いを制御および予測する上で、自由度が低いほうが簡単になる • これがまさに集約や値オブジェクトが行うこと • ビジネスロジックをカプセル化して保護するため自由度が低下し、複雑さに 対処できる -
Conclusion / 結論 • 値オブジェクト ◦ ビジネスドメインの概念でその値によってのみ識別できるので、明示的なIDフィールドは必要なく、 不変なオブジェクト ◦
データだけでなく振る舞いもモデル化する • 集約 ◦ トランザクション境界を共有するエンティティの階層 ◦ 集約の境界に含まれる全てのデータは強い整合性が必要 ◦ 集約の状態と内部のオブジェクトは集約のコマンドを実行することによってのみ変更可能 ◦ データフィールドは外部に対しては読み取り専用 ◦ ドメインイベントを公開することで外部エンティティと通信できる • ドメインサービス ◦ ドメインモデルの集約または値オブジェクトのいずれにも属さないビジネスロジックをホストするス テートレスオブジェクト -
Exercises / エクササイズ 1. 次のうちどれが正しいでしょう? a. 値オブジェクトにはデータのみを含めることができる b. 値オブジェクトに含めることができるのは振る舞いのみ c.
値オブジェクトは不変である d. 値オブジェクトの状態は変更される可能性がある -
Exercises / エクササイズ 1. 次のうちどれが正しいでしょう? a. 値オブジェクトにはデータのみを含めることができる b. 値オブジェクトに含めることができるのは振る舞いのみ c.
値オブジェクトは不変である d. 値オブジェクトの状態は変更される可能性がある -
Exercises / エクササイズ 2. 集約の境界を設計するための一般的な指針は何でしょう? a. 1つの集約には1つのエンティティのみを含めることができる。また1つの集約は 1つのデータベーストランザクションに含めることができる b.
集約はビジネスドメインのデータ整合性の要件が損なわれない限り、できるだ け小さくなるように設計する必要がある c. 集約はエンティティの階層を表す。したがって、データの一貫性を最大限に高 めるためには、集約をできるだけ広く設計する必要がある d. ビジネスドメインによっては小さな集約が最適だが、他のビジネスドメインでは できるだけ大きな集計を使用する方が効率的である -
Exercises / エクササイズ 2. 集約の境界を設計するための一般的な指針は何でしょう? a. 1つの集約には1つのエンティティのみを含めることができる。また1つの集約は 1つのデータベーストランザクションに含めることができる b.
集約はビジネスドメインのデータ整合性の要件が損なわれない限り、できるだ け小さくなるように設計する必要がある c. 集約はエンティティの階層を表す。したがって、データの一貫性を最大限に高 めるためには、集約をできるだけ広く設計する必要がある d. ビジネスドメインによっては小さな集約が最適だが、他のビジネスドメインでは できるだけ大きな集計を使用する方が効率的である -
Exercises / エクササイズ 3. 1つのトランザクションで集約の1つのインスタンスしかコミットできないのは なぜでしょう? a. 高負荷下でもモデルの実行を保証できるようにするため
b. 正しいトランザクション境界を確保するため c. そのような要件はない。ビジネスドメインによって異なる d. KeyValueStoreやドキュメントストアなど、複数レコードトランザクションをサポー トしていないデータベースを操作できるようにするため -
Exercises / エクササイズ 3. 1つのトランザクションで集約の1つのインスタンスしかコミットできないのは なぜでしょう? a. 高負荷下でもモデルの実行を保証できるようにするため
b. 正しいトランザクション境界を確保するため c. そのような要件はない。ビジネスドメインによって異なる d. KeyValueStoreやドキュメントストアなど、複数レコードトランザクションをサポー トしていないデータベースを操作できるようにするため -
Exercises / エクササイズ 4. ドメインモデルの構成要素間の関係を最もよく表しているものはどれでしょう? a. 値オブジェクトはエンティティのプロパティを記述する b.
値オブジェクトはドメインイベントを生成できる c. 集約には1つ以上のエンティティが含まれる d. aとc -
Exercises / エクササイズ 4. ドメインモデルの構成要素間の関係を最もよく表しているものはどれでしょう? a. 値オブジェクトはエンティティのプロパティを記述する b.
値オブジェクトはドメインイベントを生成できる c. 集約には1つ以上のエンティティが含まれる d. aとc -
Exercises / エクササイズ 5. アクティブレコードと集約の違いについて正しいのはどれでしょう? a. アクティブレコードはデータのみが含まれるが、集約には振る舞いも含まれる b.
集約はすべてのビジネスロジックをカプセル化するが、アクティブレコードを操作 するビジネスロジックはその境界の外側に配置できる c. 集約にはデータのみが含まれるがアクティブレコードにはデータと振る舞いの両 方が含まれる d. 集約にはアクティブレコードのセットが含まれる -
Exercises / エクササイズ 5. アクティブレコードと集約の違いについて正しいのはどれでしょう? a. アクティブレコードはデータのみが含まれるが、集約には振る舞いも含まれる b.
集約はすべてのビジネスロジックをカプセル化するが、アクティブレコードを操作 するビジネスロジックはその境界の外側に配置できる c. 集約にはデータのみが含まれるがアクティブレコードにはデータと振る舞いの両 方が含まれる d. 集約にはアクティブレコードのセットが含まれる