ヒント
このコンテンツは、.NET Docs で入手できる、またはオフラインで読み取ることができる無料のダウンロード可能な PDF として入手できる、コンテナー化された .NET アプリケーションの電子ブックである .NET マイクロサービス アーキテクチャからの抜粋です。
ビジネス マイクロサービスまたは境界コンテキストごとに 1 つのリッチ ドメイン モデルを定義します。
目標は、ビジネス マイクロサービスまたは境界コンテキスト (BC) ごとに 1 つのまとまりのあるドメイン モデルを作成することです。 ただし、BC またはビジネス マイクロサービスは、1 つのドメイン モデルを共有するいくつかの物理サービスで構成される場合があることに注意してください。 ドメイン モデルは、それが表す単一の境界コンテキストまたはビジネス マイクロサービスのルール、動作、ビジネス言語、制約をキャプチャする必要があります。
ドメイン エンティティ パターン
エンティティはドメイン オブジェクトを表し、それらを構成する属性だけでなく、主に ID、継続性、永続化によって定義されます。 Eric Evans が言うように、"主にその ID によって定義されるオブジェクトはエンティティと呼ばれます。"エンティティはモデルのベースであるため、ドメイン モデルでは非常に重要です。 そのため、慎重に識別して設計する必要があります。
エンティティの ID は、複数のマイクロサービスまたは有界コンテキストをまたがる可能性があります。
同じ ID (つまり、同じ Id 値 (同じドメイン エンティティではない場合もあります) は、複数の境界コンテキストまたはマイクロサービスにわたってモデル化できます。 ただし、これは、同じ属性とロジックを持つ同じエンティティが複数の境界コンテキストに実装されることを意味するものではありません。 代わりに、各境界コンテキストのエンティティは、その境界コンテキストのドメインで必要な属性と動作を制限します。
たとえば、購入者エンティティには、プロファイルまたは ID マイクロサービスのユーザー エンティティで定義されている人物の属性のほとんどが含まれる場合があります (ID を含む)。 ただし、特定の購入者データのみが注文プロセスに関連するため、注文マイクロサービスの購入者エンティティの属性が少なくなる可能性があります。 各マイクロサービスまたは有界コンテキストのコンテキストは、ドメイン モデルに影響します。
ドメイン エンティティは、データ属性を実装するだけでなく、動作を実装する必要があります。
DDD のドメイン エンティティは、エンティティ データ (メモリ内でアクセスされるオブジェクト) に関連するドメイン ロジックまたは動作を実装する必要があります。 たとえば、注文エンティティ クラスの一部として、注文項目の追加、データ検証、合計計算などのタスクのメソッドとしてビジネス ロジックと操作を実装する必要があります。 エンティティのメソッドは、それらのルールをアプリケーション レイヤー全体に分散するのではなく、エンティティの不変性とルールを扱います。
図 7-8 は、データ属性だけでなく、関連するドメイン ロジックを持つ操作またはメソッドを実装するドメイン エンティティを示しています。
図 7-8 データと動作を実装するドメイン エンティティ設計の例
ドメイン モデル エンティティは、メソッドを使用して動作を実装します。つまり、"貧血" モデルではありません。 もちろん、エンティティ クラスの一部としてロジックを実装していないエンティティを含めることもできます。 これは、ほとんどのロジックが集計ルートで定義されているため、子エンティティに特別なロジックがない場合に、集計内の子エンティティで発生する可能性があります。 ドメイン エンティティではなくサービス クラスにロジックが実装されている複雑なマイクロサービスがある場合は、次のセクションで説明する、貧血ドメイン モデルに分類される可能性があります。
リッチ ドメイン モデルと貧血ドメイン モデル
AnemicDomainModel の投稿で、Martin Fowler は次の方法で貧血ドメイン モデルについて説明しています。
貧弱なドメインモデルの基本的な症状は、初見で本物らしく見えることです。 ドメイン空間には名詞に名前が付けられたオブジェクトが多数存在し、これらのオブジェクトは、真のドメイン モデルが持つ豊富なリレーションシップと構造に接続されています。 ただし、オブジェクトのビヘイビアーを見れば違いがわかります。それらのオブジェクトにはわずかなビヘイビアーしかなく、ゲッターとセッターの入れ物にすぎないということに気づくと思います。
もちろん、貧血ドメイン モデルを使用する場合、これらのデータ モデルは、すべてのドメインまたはビジネス ロジックをキャプチャする一連のサービス オブジェクト (従来は ビジネス レイヤーという名前) から使用されます。 ビジネス レイヤーはデータ モデルの上に配置され、データ モデルをデータと同じように使用します。
貧血ドメイン モデルは、単なる手続き型スタイルの設計です。 貧血エンティティ オブジェクトは、動作 (メソッド) がないため、実際のオブジェクトではありません。 これらはデータ プロパティのみを保持するため、オブジェクト指向の設計ではありません。 すべての動作をサービス オブジェクト (ビジネス レイヤー) に配置することで、基本的に スパゲッティ コード または トランザクション スクリプトが得られ、ドメイン モデルが提供する利点が失われます。
マイクロサービスまたは有界コンテキストが非常に単純な (CRUD サービス) 場合でも、データ プロパティのみを持つエンティティ オブジェクトの形式の貧血ドメイン モデルは十分であり、より複雑な DDD パターンを実装する価値がない可能性があります。 その場合は、CRUD 目的のデータのみを含むエンティティを意図的に作成しているため、単なる永続化モデルになります。
そのため、マイクロサービス アーキテクチャは、各有界コンテキストに応じて、マルチアーキテクチャ アプローチに最適です。 たとえば、eShopOnContainers では、注文マイクロサービスは DDD パターンを実装しますが、単純な CRUD サービスであるカタログ マイクロサービスでは実装されません。
一部の人々は、貧血ドメインモデルはアンチパターンであると言います。 実際には、実装している内容によって異なります。 作成するマイクロサービスが十分に単純な場合 (CRUD サービスなど)、貧血ドメイン モデルに従ってもアンチパターンではありません。 ただし、絶えず変化するビジネスルールを持つマイクロサービスのドメインの複雑さに対処する必要がある場合には、貧弱なドメインモデルはそのマイクロサービスまたは有界コンテキストにとってアンチパターンになり得ます。 その場合、データと動作を含むエンティティを含むリッチ モデルとして設計し、追加の DDD パターン (集計、値オブジェクトなど) を実装すると、このようなマイクロサービスの長期的な成功に大きな利点がある可能性があります。
その他のリソース
DevIQ。 ドメイン エンティティ
https://deviq.com/entity/Martin Fowler。 ドメイン モデル
https://martinfowler.com/eaaCatalog/domainModel.htmlMartin Fowler。 貧血ドメイン モデル
https://martinfowler.com/bliki/AnemicDomainModel.html
値オブジェクト パターン
Eric Evans が説明したように、"多くのオブジェクトには概念的な ID がありません。 これらのオブジェクトは、物の特定の特性を記述します。
エンティティには ID が必要ですが、システムには、Value Object パターンのように、必要でないオブジェクトが多数存在します。 値オブジェクトは、ドメインの側面を記述する概念 ID を持たないオブジェクトです。 これらは、一時的にのみ考慮されるデザイン要素を表すためにインスタンス化するオブジェクトです。 あなたは、彼らが 誰であるか ではなく、彼らが 何であるかを 気にします。 例としては、数値や文字列が含まれますが、属性のグループなどのより高いレベルの概念になる場合もあります。
マイクロサービス内のエンティティは、別のマイクロサービス内のエンティティではない可能性があります。2 番目のケースでは、有界コンテキストの意味が異なる可能性があるためです。 たとえば、eコマース アプリケーションの住所は、個人または会社の顧客プロファイルの属性のグループのみを表す可能性があるため、ID がまったくない可能性があります。 この場合、アドレスは値オブジェクトとして分類する必要があります。 ただし、電力会社のアプリケーションでは、顧客の住所がビジネス ドメインにとって重要になる可能性があります。 そのため、請求システムを住所に直接リンクできるように、住所には ID が必要です。 その場合、アドレスはドメイン エンティティとして分類する必要があります。
名前と姓を持つユーザーは、通常、名前と姓が別の値のセットと一致する場合でも、その名前が別の人物を参照している場合など、ID を持っているため、エンティティです。
値オブジェクトは、リレーショナル データベースや Entity Framework (EF) などの ORM では管理が困難です。一方、ドキュメント指向データベースでは実装と使用が容易になります。
EF Core 2.0 以降のバージョンには、後で詳しく説明するように、値オブジェクトの処理を容易にする 所有エンティティ 機能が含まれています。
その他のリソース
Martin Fowler。 値オブジェクト パターン
https://martinfowler.com/bliki/ValueObject.htmlValue オブジェクト
https://deviq.com/value-object/Test-Driven 開発における値オブジェクト
https://leanpub.com/tdd-ebook/read#leanpub-auto-value-objectsEric Evans。 Domain-Driven 設計: ソフトウェアの中心にある複雑さに取り組む。 (書籍。値オブジェクトの説明が含まれます)
https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215/
集計パターン
ドメイン モデルには、注文フルフィルメントやインベントリなど、機能の重要な領域を制御できるさまざまなデータ エンティティとプロセスのクラスターが含まれています。 よりきめ細かい DDD 単位は、凝集単位として扱うことができるエンティティと動作のクラスターまたはグループを記述する集計です。
通常、必要なトランザクションに基づいて集計を定義します。 従来の例は、注文項目の一覧も含まれる注文です。 通常、注文アイテムはエンティティになります。 ただし、注文集約内では子エンティティとなり、注文エンティティもそのルート エンティティとして含まれます (通常、ルート エンティティは、集約ルートと呼ばれます)。
集合の特定は難しいことがあります。 集約は、一貫性を持たなければならないオブジェクトのグループですが、オブジェクトのグループを選択して集約にラベルを付けることはできません。 ドメインの概念から始めて、その概念に関連する最も一般的なトランザクションで使用されるエンティティについて考える必要があります。 トランザクション的に一貫性を保つ必要があるエンティティは、集計を形成します。 おそらく、トランザクション操作について考えるのが、集計を識別するための最良の方法です。
集約根またはルート エンティティ パターン
集約は、少なくとも 1 つのエンティティ (ルート エンティティまたはプライマリ エンティティとも呼ばれます) で構成されます。 さらに、複数の子エンティティと値オブジェクトを含め、すべてのエンティティとオブジェクトが連携して必要な動作とトランザクションを実装できます。
集約ルートの目的は、集計の一貫性を確保することです。集計ルート クラスのメソッドまたは操作を使用して集計を更新する唯一のエントリ ポイントである必要があります。 集計ルートを介してのみ、集計内のエンティティに変更を加える必要があります。 これは集計の整合性ガーディアンであり、集計で準拠する必要がある可能性があるすべての不変条件と整合性規則を考慮します。 子エンティティまたは値オブジェクトを個別に変更した場合、集計ルートは集計が有効な状態であることを確認できません。 本来の機能を発揮できなくなるでしょう。 整合性の維持は、集約ルートの主な目的です。
図 7-9 では、1 つのエンティティ (集約ルート 購入者) を含む購入者集計などのサンプル集計を確認できます。 注文集計には、複数のエンティティと値オブジェクトが含まれています。
図 7-9 複数のエンティティまたは 1 つのエンティティを含む集計の例
DDD ドメイン モデルは集計から構成され、集計には 1 つ以上のエンティティのみを含めることができます。また、値オブジェクトも含めることができます。 Buyer 集計には、eShopOnContainers 参照アプリケーションの注文マイクロサービスと同様に、ドメインに応じて追加の子エンティティが含まれる場合があることに注意してください。 図 7-9 は、集約ルートのみを含む集計の例として、購入者が 1 つのエンティティを持つ場合を示しています。
集計の分離を維持し、それらの間の境界を明確に保つために、DDD ドメイン モデルでは、eShopOnContainers の Ordering マイクロサービス ドメイン モデルに実装されているように、集計間の直接ナビゲーションを禁止し、外部キー (FK) フィールドのみを持つことをお勧めします。 Order エンティティには購入者用の外部キー フィールドしかありませんが、次のコードに示すように EF Core ナビゲーション プロパティはありません。
public class Order : Entity, IAggregateRoot
{
private DateTime _orderDate;
public Address Address { get; private set; }
private int? _buyerId; // FK pointing to a different aggregate root
public OrderStatus OrderStatus { get; private set; }
private readonly List<OrderItem> _orderItems;
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
// ... Additional code
}
集計を特定して使用するには、調査と経験が必要です。 詳細については、次のその他のリソースの一覧を参照してください。
その他のリソース
Vaughn Vernon。 効果的な集計設計 - パート 1: 1 つの集計をモデル化する ( https://dddcommunity.org/から)
https://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_1.pdfVaughn Vernon。 効果的な集計設計 - パート 2: 集計を連携させる ( https://dddcommunity.org/から)
https://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_2.pdfVaughn Vernon。 効果的な集計設計 - パート 3: 検出を通じて洞察を得る ( https://dddcommunity.org/から)
https://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_3.pdfSergey Grybniak. DDD 戦術設計パターン
https://www.codeproject.com/Articles/1164363/Domain-Driven-Design-Tactical-Design-Patterns-PartChris Richardson。 集計を使用したトランザクション マイクロサービスの開発
https://www.infoq.com/articles/microservices-aggregates-events-cqrs-part-1-richardsonDevIQ。 集計パターン
https://deviq.com/aggregate-pattern/
.NET