DDD 指向マイクロサービスの設計

ヒント

このコンテンツは eBook の「コンテナー化された .NET アプリケーションの .NET マイクロサービス アーキテクチャ」からの抜粋です。.NET Docs で閲覧できるほか、PDF として無料ダウンロードすると、オンラインで閲覧できます。

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

ドメイン駆動設計 (DDD) は、ユーザーのユース ケースに則したビジネスの現状に基づくモデリングを提唱します。 アプリケーションの構築のコンテキストで、DDD は問題をドメインと呼んで論じます。 独立した問題領域のことを境界付けられたコンテキストと言い (境界コンテキストはそれぞれ特定のマイクロサービスに関連します)、共通の言語を使ってこれらの問題を論じることを重視します。 また、内部実装をサポートするための多くの技術的概念とパターンも提案します。たとえば、(ドメイン モデル貧血症ではなく) 豊富なモデルを持つドメイン エンティティ、値オブジェクト、集約、および集約ルート (またはルート エンティティ) のルールなどです。 このセクションでは、それらの内部パターンの設計と実装を紹介します。

これらの DDD の技術的なルールとパターンは、DDD のアプローチを実装する上で学習が困難な障害と見なされることがあります。 しかし、重要な部分はパターンそのものではなく、ビジネス上の問題に沿ってコードを組織することと、同じビジネス用語 (ユビキタス言語) を使用することです。 また、DDD のアプローチを適用する必要があるのは、重要なビジネス ルールを持つ複雑なマイクロサービスを実装する場合だけです。 CRUD サービスなどのより単純な機能は、より単純なアプローチで管理できます。

マイクロサービスを設計および定義する際は、どこに境界線を引くかが主な課題です。 DDD のパターンは、ドメインが持つ複雑さを理解するために役立ちます。 ドメイン モデルでは、境界付けられたコンテキストごとに、ドメインをモデル化するためのエンティティ、値オブジェクト、集約を特定および定義します。 ドメイン モデルを構築および改善すると、それは境界の中に組み込まれ、境界はコンテキストを定義します。 これは明示的であり、マイクロサービスの形を取ります。 これらの境界内のコンポーネントは、最終的にマイクロサービスになります。ただし、場合によっては、BC またはビジネス マイクロサービスがいくつかの物理サービスから構成されることもあります。 DDD では境界が重要であり、マイクロサービスにとってもまたそうです。

マイクロサービスのコンテキスト境界を比較的小さく保つ

境界付けられたコンテキストの境界をどこに引くかを決める際は、2 つの相反する目標のバランスをとります。 第一の点として、可能な限り小さいマイクロサービスを作ることがまず望まれます。しかし、これを主な原動力とすべきではありません。ひとまとめにすべきものが一緒になるように境界を引きます。 第二の点として、マイクロサービス間の通信が多くなりすぎないようにする必要があります。 これらの目標は、相反する可能性があります。 両者のバランスをとる必要があり、そのためには、システムをできるだけ多くの小さなマイクロサービスに分解していき、境界付けられたコンテキストを新しく分離しようとするたびに通信境界が急増する地点にまで至る必要があります。 1 つの境界付けられたコンテキストの中で鍵となるのは、まとまりです。

これは、クラスを実装する場合の、不適切な関係というコードのにおいに似ています。 2 つのマイクロサービスで互いに多くの協働が必要な場合は、両者を同じマイクロサービスにまとめた方が望ましいと考えられます。

この側面に対する別の観点となるのは、自律性です。 あるマイクロサービスが別のサービスに依存しなければ要求に直接対応できない場合、真に自律的であるとは言えません。

DDD マイクロサービス内のレイヤー

ビジネス上および技術上の複雑性が高いエンタープライズ アプリケーションは、ほとんどの場合、複数のレイヤーで定義されます。 レイヤーとは、論理的な成果物であり、サービスの展開には関連しません。 これが存在しているのは、開発者がコードの複雑さを管理できるようにするためです。 異なるレイヤーは (ドメイン モデル レイヤーとプレゼンテーション レイヤーなど) 種類も異なる場合があります。その場合はそれらの種類間の変換が必要です。

たとえば、データベースから読み込まれるエンティティがあるとします。 次いで、この情報の一部、または他のエンティティからの追加データを含む情報の集約が、REST Web API を介してクライアント UI に送信される可能性があります。 ここでポイントとなるのは、ドメイン エンティティはドメイン モデル レイヤーに含まれており、プレゼンテーション レイヤーなど、自分が属さない他の領域に伝搬されてはならないということです。

さらに、集約ルート (ルート エンティティ) によって制御される、常に有効なエンティティ (「ドメイン モデル レイヤーでの検証の設計」セクションを参照) が必要です。 したがって、エンティティをクライアント ビューにバインドしてはなりません。これは、UI レベルでは一部のデータがまだ検証されていないためです。 そのため、ViewModel が存在します。 ViewModel は、専らプレゼンテーション レイヤーのニーズに応えるデータ モデルです。 ドメイン エンティティは ViewModel に直接属してはいません。 代わりに、ViewModel からドメイン エンティティ、あるいはその逆に変換する必要があります。

複雑性に対処するには、ドメイン モデルを集約ルートで管理することが重要です。これにより、そのエンティティのグループ (集約) に関係する不変条件とルールすべてが、確実に単一のエントリ ポイントまたはゲート (集約ルート) を通して実行されます。

図 7-5 は、階層化設計が eShopOnContainers アプリケーションにどのように実装されているかを示しています。

Diagram showing the layers in a domain-driven design microservice.

図 7-5。 eShopOnContainers 内の注文マイクロサービスの DDD レイヤー

Ordering などの DDD マイクロサービスの 3 つのレイヤー。 レイヤーはそれぞれ VS プロジェクトです。アプリケーション レイヤーは Ordering.API、ドメイン レイヤーは Ordering.Domain、インフラストラクチャ レイヤーは Ordering.Infrastructure です。 システムは、各レイヤーが他の特定のレイヤーとだけ通信するように設計する必要があります。 各レイヤーが異なるクラス ライブラリとして実装されている場合、この方法の適用がより簡単になる可能性があります。これは、ライブラリ間に設定されている依存関係を明確に識別できるためです。 たとえば、ドメイン モデル レイヤーは他のレイヤーに依存関係を持ってはなりません (ドメイン モデル クラスは単純な Plain Old Class Object (POCO) クラスでなければなりません)。 図 7-6 に示すように、Ordering.Domain レイヤー ライブラリは .NET ライブラリまたは NuGet パッケージだけに依存関係があり、データ ライブラリや永続化ライブラリなどの他のカスタム ライブラリには依存関係がありません。

Screenshot of Ordering.Domain dependencies.

図 7-6。 レイヤーをライブラリとして実装すると、レイヤー間の依存関係の制御が改善できる

ドメイン モデル レイヤー

Eric Evans の名著「Domain Driven Design」(ドメイン駆動設計) には、ドメイン モデル レイヤーとアプリケーション レイヤーについて、次のように記述されています。

ドメイン モデル レイヤー:ビジネスの概念、ビジネス状況に関する情報、ビジネス ルールの表現を担当します。 ビジネス状況を反映する状態はここで制御および使用されますが、その状態を保管することの技術的な詳細はインフラストラクチャに委任されます。 このレイヤーは、ビジネス ソフトウェアの中核です。

ドメイン モデル レイヤーは、ビジネスを表現する場です。 マイクロサービスのドメイン モデル レイヤーを .NET で実装する場合、そのレイヤーはデータと動作をキャプチャするドメイン エンティティを持つクラス ライブラリとしてコード化されます (ロジックを持つメソッド)。

永続化非依存の原則とインフラストラクチャ非依存の原則に従って、このレイヤーではデータ永続化の詳細を完全に無視する必要があります。 これらの永続化タスクは、インフラストラクチャ レイヤーによって実行する必要があります。 このため、このレイヤーはインフラストラクチャへの直接的な依存関係を持ってはなりません。つまり、重要なルールは、ドメイン モデルのエンティティ クラスは POCO にする必要があるということです。

ドメイン エンティティには、Entity Framework や NHibernate などのデータ アクセス インフラストラクチャ フレームワークへの直接的な依存関係 (たとえば、基底クラスからの派生) があってはなりません。 ドメイン エンティティが、どのインフラストラクチャ フレームワークで定義されたどの型から派生したものでもなく、それを実装したものでもないことが理想的です。

Entity Framework Core などの最新の ORM フレームワークのほとんどではこのアプローチが可能であり、ドメイン モデル クラスがインフラストラクチャに結合されないようになっています。 しかし、特定の NoSQL データベースとフレームワークを使用する場合 (Azure Service Fabric の Actors と Reliable Collections など) は、POCO エンティティを使用することがいつでも可能とは限りません。

ドメイン モデルで永続化非依存の原則に従うことが重要な場合でも、永続化に関する問題を無視してはなりません。 物理的なデータ モデルと、それがどのようにエンティティ オブジェクト モデルにマップされるかを理解しておくことが引き続き重要です。 そうしないと、不可能な設計を作成してしまう可能性があります。

また、この側面は、リレーショナル データベース用に設計されたモデルを採用して、それを NoSQL またはドキュメント指向のデータベースに直接移行できるという意味ではありません。 このモデルが適合するエンティティ モデルもありますが、たいていは適合しません。 ストレージ テクノロジと ORM テクノロジの両方に基づき、エンティティ モデルが従う必要のある制約はまだあります。

アプリケーション レイヤー

アプリケーション レイヤーに移ります。ここでも、Eric Evans の著書「Domain Driven Design」の引用を示します。

アプリケーション レイヤー: ソフトウェアが実行するはずのジョブを定義し、表現力が豊かなドメイン オブジェクトに問題解決を指示します。 このレイヤーが担当するタスクは、ビジネスにとって重要であるか、他のシステムのアプリケーション レイヤーとの対話に必要です。 このレイヤーは小さなものに保ちます。 これにはビジネス ルールもナレッジも含まれません。これは、ただ各タスクを調整して、下方にある次のレイヤーのドメイン オブジェクトのコラボレーションに作業を委任するに過ぎません。 このレイヤーは、ビジネス状況を反映する状態を持ちませんが、ユーザーまたはプログラムのタスクの進行状況を反映する状態を持つことはできます。

.NET 内のマイクロサービスのアプリケーション レイヤーは、一般に ASP.NET Core Web API プロジェクトとしてコーディングされます。 プロジェクトでは、マイクロサービスの相互作用、リモート ネットワーク アクセス、UI またはクライアント アプリから使用される外部の Web API が実装されます。 これには、クエリ (CQRS アプローチを使用する場合)、マイクロサービスによって受け付けられるコマンド、さらにはマイクロサービス (統合イベント) 間のイベント駆動通信さえも含まれます。 アプリケーション レイヤーを表す ASP.NET Core Web API には、ビジネス ルールもドメインに関するナレッジも含まれていてはなりません (特にトランザクションまたは更新のドメイン ルール)。つまり、これらを所有するのはドメイン モデル クラス ライブラリでなければなりません。 アプリケーション レイヤーでは、各タスクの調整だけを行うべきであり、ドメインの状態 (ドメイン モデル) の保持も定義も行ってはなりません。 ビジネス ルールの実行はドメイン モデル クラス自体 (集約ルートとドメイン エンティティ) に委任され、その結果、それらのドメイン エンティティ内でデータが最終的に更新されます。

基本的に言って、アプリケーション ロジックには、特定のフロント エンドを利用するすべてのユース ケースを実装します。 たとえば、Web API サービスに関連する実装です。

目標は、ドメイン モデル レイヤー内のドメイン ロジック、その不変条件、データ モデル、関連するビジネス ルールを、プレゼンテーション レイヤーとアプリケーション レイヤーから完全に独立させなければならないということです。 第一に、ドメイン モデル レイヤーは、どのインフラストラクチャ フレームワークにも直接的に依存していてはなりません。

インフラストラクチャ レイヤー

インフラストラクチャ レイヤーは、最初にドメイン エンティティ (メモリ内) に保持されているデータを、データベースまたは別の永続ストアに永続化する方法を表します。 一例は、Entity Framework Core コードを使用して、DbContext でリレーショナル データベースにデータを永続化するリポジトリ パターン クラスを実装することです。

上で説明した永続化非依存の原則とインフラストラクチャ非依存の原則に従って、インフラストラクチャ レイヤーはドメイン モデル レイヤーを "汚染" してはなりません。 ドメイン モデル エンティティ クラスは、データの保存に使用するインフラストラクチャ (EF または他のフレームワーク) にとらわれないようにしておく必要があります。そのために、フレームワークに対する強い依存関係を持たせないようにします。 ドメイン モデル レイヤー クラス ライブラリには、ソフトウェアの中核部分を実装するドメイン コード、つまり POCO エンティティ クラスだけを含め、インフラストラクチャ テクノロジからは完全に分離させる必要があります。

したがって、図 7-7 に示すように、レイヤーまたはクラス ライブラリとプロジェクトは、最終的にドメイン モデル レイヤー (ライブラリ) に依存するはずで、逆方向の依存はありません。

Diagram showing dependencies that exist between DDD service layers.

図 7-7。 DDD 内のレイヤー間の依存関係

DDD サービスの依存関係、アプリケーション レイヤーはドメインとインフラストラクチャに依存し、インフラストラクチャはドメインに依存しますが、ドメインはレイヤーに依存しません。 このレイヤー設計は、マイクロサービスごとに独立している必要があります。 上述のとおり、最も複雑なマイクロサービスは DDD のパターンに従って実装できますが、単純なデータ駆動マイクロサービス (単一レイヤーの単純な CRUD) はより単純な方法で実装します。

その他の技術情報