Domain-driven programming
I. はじめに
皆さんに馴染みのある MVC レイヤーアーキテクチャと比較すると、ドメイン駆動設計は複雑なビジネスシステムや継続的な反復が必要なソフトウェアシステムのアーキテクチャモデルにより適しています。ドメイン駆動設計の概念や利点については、参照可能な文献が数多くあり、関連書籍を読まれた方も多いでしょう。そのため、本記事ではドメイン駆動設計の概要について簡単に紹介します。
AliHealth に参画した後、私のチームもドメイン駆動設計の適用を積極的に推進しています。関連するメンバーから優れたスキャフォールディングコードも提供されていますが、現状の実装状況は理想的とは言えません。私見ですが、この結果の主な理由は 4 つあります。
誰もが MVC プログラミングモードに慣れ親しんでいるため、特定の機能を実装する必要がある場合、より安全で馴染みのある方法を使いがちです。
ドメイン駆動プログラミングの書き方について統一された理解ができていません(Axon Framework[1] はドメイン駆動設計を非常にうまく実装していますが、やや「重量級」すぎます)。
DDD 自体の実装は難易度が高く、完璧に実現するにはイベント駆動とイベントストアが必要になることが多いですが、これら 2 つは私たちが日常的に使うものではありません。
ドメイン駆動設計は複雑なシステムを対象としており、ビジネス開発の初期段階は比較的単純に見えるため、いきなりドメイン駆動設計を持ち出すと過剰設計の嫌疑がかかります。これが、ドメイン駆動設計がシステムのリラファクタリングが必要になった時にのみ議論されることが多い理由です。
筆者は研究開発プロセスにおいてドメイン駆動プログラミングの調査と実践を行い、ドメイン駆動フレームワークである Axon Framework についても深い理解を持っています。(ビジネスシナリオが比較的単純だったこともあるかもしれませんが)当時の導入効果は悪くありませんでした。アーキテクトの視点だけでなく、フロントラインの開発者の視点から見ても、ドメイン駆動プログラミングのコアとなる利点は以下の通りです。
*オブジェクト指向プログラミングモデルを実装し、高凝集・低結合を実現する
*複雑なビジネスシステムの反復プロセスにおいて、コード構造が際限なく混沌化することを防ぎ、システムの持続的なメンテナンス性を保証する
*もちろん、ドメイン駆動開発で最も重要なのはドメインを正しく分割することです。この分割は理論の指導のもと、設計者のビジネスに対する深い分析と十分な理解を組み合わせて行うことができます。本記事では、開発前にドメイン分割が既に行われていることを前提とし、コーディング段階でどのように実践してドメイン駆動の利点を発揮させるかに焦点を当てます。
II. 保険分野の知識紹介
保険ビジネスを例にプログラミング実践を行います。保険分野の高度に抽象化された分割を図に示します。ユースケース分析を通じて、ビジネス全体をプロダクトドメイン、引受、保険証券発行、保険金請求などのドメイン(境界づけられたコンテキスト)に分割し、各ドメインはビジネスの発展に応じてサブドメインに分割できます。もちろん、実際の保険ビジネスは図に示したものよりもはるかに複雑ですが、ここではビジネス知識の一節としての紹介は行わず、後続のコード実践を容易にするためのみにとどめます。
III. ドメイン駆動開発のコード構造
1. ドメイン駆動コードの階層化
異なる Java プロジェクトを使用して異なるマイクロサービスをデプロイすることでドメインを分離することも、同じ Java プロジェクト内で異なるモジュールを使用してドメインを分離することもできます。ここではモジュールを使用してドメイン分離を実装します。ただし、どのような方法でドメイン分離を行う場合でも、ドメイン間の相互作用は相手側の二方パッケージまたは API 層が提供する HTTP サービスを使用する必要があり、他のドメイン内のサービスを直接参照することはできません。
各ドメイン内では、MVC のアプリケーション三層アーキテクチャの分割と比較して、ドメイン駆動設計はアプリケーションモジュールを図に示す 4 層に分割します。
ユーザーインターフェース層
外部ユーザーまたはシステムに直接対応し、外部からの入力を受け取って結果を返す責務を持ちます。二方パッケージの実装クラス、Spring MVC のコントローラー、特定のデータビューコンバーターなどは通常この層に位置します。コードレベルで頻繁に使用されるパッケージ名は、interface、api、facade などです。ユーザーインターフェース層の入出力パラメーターは POJO スタイルで定義されます。
ユーザーインターフェース層はビジネスロジックを含まない薄い層です。セキュリティ認証、簡易的な入力パラメーター検証(@Valid アノテーションの使用など)、アクセスログ記録、統一された例外処理ロジック、および統一された戻り値のカプセル化は、この層で完了させるべきです。
ユーザーインターフェース層が必要とする機能の実装はアプリケーション層が担当し、ここで依存性逆転を行う必要は一般的にありません。コーディング時、この層はアプリケーション層で定義されたインターフェースを直接参照できるため、この層はアプリケーション層に依存します。注意点として、理論上はユーザーインターフェース層がドメイン層とインフラストラクチャ層の機能を直接使用できますが、この使い方に慣れるまでは厳密な階層アーキテクチャを採用することをお勧めします。つまり、現在の層はその直下の隣接層にのみ依存するという原則です。
アプリケーション層
アプリケーション層はインターフェース層で必要とされる機能を具体的に実装しますが、この層は実際のビジネスルールを実装するのではなく、実際のユースケースに応じてドメイン層が提供する機能の調整と呼び出しを行います。
メッセージ送信、イベントモニタリング、トランザクション制御などはこの層で実装することをお勧めします。コードレベルで頻繁に使用されるパッケージ名は、application、service、manager などです。Spring MVC のサービス層に代わり、ビジネスロジックをドメイン層に移管する役割です。
ドメイン層
ドメイン層はオブジェクト指向であり、ドメイン内のオブジェクトが持つ本来の機能を反映・実現するために使用されます。したがって、ドメイン駆動プログラミングでは、ドメイン層のプログラミング実装が他の外部オブジェクトに依存することは許されません。ドメイン層のプログラミングは、ドメイン内のオブジェクトに対して私たちが持つ本来の機能と、現在のビジネスシナリオでどのような振る舞いを見せるかに基づいて行われます。これらの機能を十分に理解した後、直接コーディングできます。
たとえば、オブジェクト指向プログラミングに初めて触れた時によく遭遇する例として、鳥は飛べて犬は泳げるというものがあります。ビジネスドメインがこれらのオブジェクトの移動のみに関心がある場合、以下のような実装ができます。
ドメイン駆動プログラミングでは、このようにオブジェクトの機能を実装する必要があります(充血モデル)。MVC アーキテクチャでよく使われる貧血モデルのように、サービスにビジネスロジックを記述するのではありません。
もちろん、このようなプログラミング方法を採ったとしても、ドメイン駆動の実現にはまだ程遠く、一見単純な問題が大きな不安をもたらす可能性があります。たとえば、複雑なオブジェクトの初期化と永続化はどのように行うべきか。同じものが異なるドメインに存在するが焦点が異なる場合、そのオブジェクトをどのように抽象化すべきか。異なるドメインのオブジェクトが互いの情報を必要とする場合、どのように取得すべきか。
これらの問題について、コード例のセクションで参考となる解決策を提示していきます。
インフラストラクチャ層
インフラストラクチャ層は上記の各層に共通の技術機能を提供します。たとえば、モニタリングやメッセージ送信の機能、データベース・キャッシュ・NoSQL データベース・ファイルシステムの CRUD 機能などです。
2. まとめ
ドメイン駆動設計の各層をさらに分析すると、より具体的な階層構造は以下のようになります。
上記の階層化原則に基づき、前述の保険分野で参照できるコード構造は以下のようになります。各サブパッケージの概念と機能については、後続のコーディング例で詳しく説明します。
IV. ドメイン駆動開発コード
理論上、ドメイン層は他の層に依存せずビジネスのコアであるため、まずドメイン層のコードを記述すべきです。しかし、保険分野に関する知識が不足しているため、保険証券がどのような本来の機能を持っているかわからない可能性があります。そこで、ユースケースの力を借りてコードを直接示します。
1. ユースケース
ユーザーがフロントエンドページで保険商品を選択し、オプションの保障責任を選択し、申込者・被保険者の情報を入力し、決済方法(分割払い・一括払いなど)を選択して支払い後に保険申込を提出します。
サーバーが保険申込を受け付け → 引受 → 保険証券発行 → 保険証券給付の支払いを行います。
ここでユースケース 1 はユースケース 2 の前置ユースケースです。ユースケース 1 は既に正常に完了している(ユースケース 1 で保険料計算が完了している)と仮定し、ユースケース 2 のみを実装します。ユースケース 2 は大まかな実装にとどめ、コードスタイルを示すことだけを目的とします。
2. ユーザーインターフェース層のプログラミング実践
サブパッケージ構成
うち、client は insurance-client(公開二方パッケージ)の実装であり、web は REST スタイルのインターフェースの実装です。
ここで使用される入力パラメーターと戻り値のクラスはアプリケーション層で定義されています。
3. アプリケーション層のプログラミング実践
1. サブパッケージ構成
うち、最外層のインターフェースは特定のビジネスシナリオに対応しており、ビジネスの発展に応じてサブパッケージ分割できます。
pojo パッケージでは、アプリケーション層で使用されるさまざまなデータクラス(前述の IssuePolicyRequest など)と、他の層に伝播する際に型変換が必要なコンバーターを定義します。
tasks パッケージでは、一部のタイミングタスクのエントリーを定義します。
ドメインプログラミングの実践では大量の型変換が必要になる点に注意してください。MapStruct[2] などのフレームワークを使用して、これらの型変換がもたらす煩雑な作業を軽減できます。
2. ユースケースコード
ここのコードは、アプリケーション層がユースケース 2 をどのように処理するかを示しています。
ドメイン層のファクトリークラスを使用して Policy 集約を構築します。複雑なオブジェクトを渡す必要がある場合、型コンバーターを使用してアプリケーション層のデータクラスをドメイン層のエンティティクラスまたは値オブジェクトに変換する必要があります。
ドメイン層のサービスを使用して注文プロセスを制御します。
注文成功メッセージを送信すると、他のドメインは関心のあるメッセージを受信した際に反応します。
4. ドメイン層のプログラミング実践
1. サブパッケージ構成
ドメイン層には合計 5 つの一次サブパッケージがあります。
Anticorruption はドメイン腐敗防止層であり、現在のドメインが他のドメインや外部の情報を知る必要がある場合の、他ドメインの二方パッケージのカプセル化です。コードレベルの観点からは、腐敗防止層により、外部クライアントを呼び出す際のドメイン内での複雑なパラメーター組み立てや結果変換を回避できます。
factory は複雑な集約の初期化問題を解決します。ドメインモデルを外部呼び出し向けに設計しますが、外部もこのオブジェクトの組み立て方法を使用する必要がある場合、オブジェクトの内部構造を知る必要があります。これは呼び出し側の開発にとって非常に親切ではありません。さらに、複雑なオブジェクトや集約内のドメイン知識(ビジネスルール)は満たされる必要があります。外部が独自に複雑なオブジェクトや集約を組み立てることを許可すると、ドメイン知識が呼び出し側のコードに漏洩してしまいます。ここで注意すべきは、これは主に集約やエンティティに必要なデータを入力するものであり、オブジェクトの動作に関わるものではないという点です。
したがって、ここでのファクトリーの核心的な役割は、集約やエンティティの初期化に必要な外部データをあらゆる場所から取得することです。
model にはドメインオブジェクトの定義が含まれます。vo パッケージではドメイン内で使用される値オブジェクトを定義します。PolicyProduct という保険商品クラスがあるのがわかります。保険の分野では、保険証券に関連する特定の商品とそのスナップショット情報に関心を寄せます。そのため、ここに保険証券用の保険商品クラスを定義しています。腐敗防止層は、プロダクトドメインから取得した保険商品信息を、私たちが関心を持つ保険証券の保険商品クラスオブジェクトに変換する責務を持ちます。
ドメイン駆動設計のベストプラクティスによると、ドメインオブジェクトモデル内での services や repositories による外部情報の取得は許可されていません。その核心的な概念は、初期化が完了した完全なエンティティが何ができるか、またはどのような状態変化を経験した後に何が起こるかということです。
repository はストレージパッケージであり、ストレージインターフェースのみを定義し、具体的な実装には関与しません。具体的な実装はインフラストラクチャ層に委ねられます。これが依存性逆転の考え方です。
service はドメインサービスであり、ドメインオブジェクトに属さない動作のうち必要な操作、たとえば一部のプロセス制御などを定義します。
2. ユースケースコード
ここで policyRepository.save(policy); の行をコメントアウトしている点に注意してください。save と create を区別する理由は何でしょうか。
save はドメイン駆動設計において最も正しいアプローチです。集約やエンティティに変更があれば、ストレージはそれが新規か更新かを気にせず、ただ保存すればよいという考え方です。理屈上は美しいですが、リレーショナルデータベースのストレージには非常に不親切です。したがって、私たちのシナリオでは、本に書かれているいわゆるベストプラクティスに背く必要があります。リポジトリに対して作成か更新かを伝え、さらに更新の場合はどの列を更新するかまで指定します。
さらに、ドメイン駆動のベストプラクティスはイベント駆動に基づいており、Axon Framework はそれを完璧に実装しています。アプリケーション層が IssuePolicyCommand というコマンドを発行し、ドメイン層がそのコマンドを受け取ります。保険証券作成後に PolicyIssuedEvent を発行します。このイベントはモニタリングされ、イベントストアに永続化されます。現時点ではここでこの方法を実装する見込みは低いため、これ以上の紹介は行いません。
V. インフラストラクチャ層のプログラミング実践
1. サブパッケージ構成
ここではリポジトリの実装のみを示しますが、実際には RPC 呼び出し用の二方パッケージの実装クラス注入など多くの機能があります。前述の通り、ドメイン層はストレージの実装に関与せず、インフラストラクチャ層が担当します。インフラストラクチャ層は必要に応じてリレーショナルデータベース、キャッシュ、NoSQL を使用でき、ドメイン層はそれを認識しません。ここではリレーショナルデータベースを例にとります。dao と dataobject は mybatis generator などのツールで生成できます。Convertor はドメインオブジェクトと dataobject の間の変換を担当します。
2. ユースケースコード
皆さんに馴染みのある MVC レイヤーアーキテクチャと比較すると、ドメイン駆動設計は複雑なビジネスシステムや継続的な反復が必要なソフトウェアシステムのアーキテクチャモデルにより適しています。ドメイン駆動設計の概念や利点については、参照可能な文献が数多くあり、関連書籍を読まれた方も多いでしょう。そのため、本記事ではドメイン駆動設計の概要について簡単に紹介します。
AliHealth に参画した後、私のチームもドメイン駆動設計の適用を積極的に推進しています。関連するメンバーから優れたスキャフォールディングコードも提供されていますが、現状の実装状況は理想的とは言えません。私見ですが、この結果の主な理由は 4 つあります。
誰もが MVC プログラミングモードに慣れ親しんでいるため、特定の機能を実装する必要がある場合、より安全で馴染みのある方法を使いがちです。
ドメイン駆動プログラミングの書き方について統一された理解ができていません(Axon Framework[1] はドメイン駆動設計を非常にうまく実装していますが、やや「重量級」すぎます)。
DDD 自体の実装は難易度が高く、完璧に実現するにはイベント駆動とイベントストアが必要になることが多いですが、これら 2 つは私たちが日常的に使うものではありません。
ドメイン駆動設計は複雑なシステムを対象としており、ビジネス開発の初期段階は比較的単純に見えるため、いきなりドメイン駆動設計を持ち出すと過剰設計の嫌疑がかかります。これが、ドメイン駆動設計がシステムのリラファクタリングが必要になった時にのみ議論されることが多い理由です。
筆者は研究開発プロセスにおいてドメイン駆動プログラミングの調査と実践を行い、ドメイン駆動フレームワークである Axon Framework についても深い理解を持っています。(ビジネスシナリオが比較的単純だったこともあるかもしれませんが)当時の導入効果は悪くありませんでした。アーキテクトの視点だけでなく、フロントラインの開発者の視点から見ても、ドメイン駆動プログラミングのコアとなる利点は以下の通りです。
*オブジェクト指向プログラミングモデルを実装し、高凝集・低結合を実現する
*複雑なビジネスシステムの反復プロセスにおいて、コード構造が際限なく混沌化することを防ぎ、システムの持続的なメンテナンス性を保証する
*もちろん、ドメイン駆動開発で最も重要なのはドメインを正しく分割することです。この分割は理論の指導のもと、設計者のビジネスに対する深い分析と十分な理解を組み合わせて行うことができます。本記事では、開発前にドメイン分割が既に行われていることを前提とし、コーディング段階でどのように実践してドメイン駆動の利点を発揮させるかに焦点を当てます。
II. 保険分野の知識紹介
保険ビジネスを例にプログラミング実践を行います。保険分野の高度に抽象化された分割を図に示します。ユースケース分析を通じて、ビジネス全体をプロダクトドメイン、引受、保険証券発行、保険金請求などのドメイン(境界づけられたコンテキスト)に分割し、各ドメインはビジネスの発展に応じてサブドメインに分割できます。もちろん、実際の保険ビジネスは図に示したものよりもはるかに複雑ですが、ここではビジネス知識の一節としての紹介は行わず、後続のコード実践を容易にするためのみにとどめます。
III. ドメイン駆動開発のコード構造
1. ドメイン駆動コードの階層化
異なる Java プロジェクトを使用して異なるマイクロサービスをデプロイすることでドメインを分離することも、同じ Java プロジェクト内で異なるモジュールを使用してドメインを分離することもできます。ここではモジュールを使用してドメイン分離を実装します。ただし、どのような方法でドメイン分離を行う場合でも、ドメイン間の相互作用は相手側の二方パッケージまたは API 層が提供する HTTP サービスを使用する必要があり、他のドメイン内のサービスを直接参照することはできません。
各ドメイン内では、MVC のアプリケーション三層アーキテクチャの分割と比較して、ドメイン駆動設計はアプリケーションモジュールを図に示す 4 層に分割します。
ユーザーインターフェース層
外部ユーザーまたはシステムに直接対応し、外部からの入力を受け取って結果を返す責務を持ちます。二方パッケージの実装クラス、Spring MVC のコントローラー、特定のデータビューコンバーターなどは通常この層に位置します。コードレベルで頻繁に使用されるパッケージ名は、interface、api、facade などです。ユーザーインターフェース層の入出力パラメーターは POJO スタイルで定義されます。
ユーザーインターフェース層はビジネスロジックを含まない薄い層です。セキュリティ認証、簡易的な入力パラメーター検証(@Valid アノテーションの使用など)、アクセスログ記録、統一された例外処理ロジック、および統一された戻り値のカプセル化は、この層で完了させるべきです。
ユーザーインターフェース層が必要とする機能の実装はアプリケーション層が担当し、ここで依存性逆転を行う必要は一般的にありません。コーディング時、この層はアプリケーション層で定義されたインターフェースを直接参照できるため、この層はアプリケーション層に依存します。注意点として、理論上はユーザーインターフェース層がドメイン層とインフラストラクチャ層の機能を直接使用できますが、この使い方に慣れるまでは厳密な階層アーキテクチャを採用することをお勧めします。つまり、現在の層はその直下の隣接層にのみ依存するという原則です。
アプリケーション層
アプリケーション層はインターフェース層で必要とされる機能を具体的に実装しますが、この層は実際のビジネスルールを実装するのではなく、実際のユースケースに応じてドメイン層が提供する機能の調整と呼び出しを行います。
メッセージ送信、イベントモニタリング、トランザクション制御などはこの層で実装することをお勧めします。コードレベルで頻繁に使用されるパッケージ名は、application、service、manager などです。Spring MVC のサービス層に代わり、ビジネスロジックをドメイン層に移管する役割です。
ドメイン層
ドメイン層はオブジェクト指向であり、ドメイン内のオブジェクトが持つ本来の機能を反映・実現するために使用されます。したがって、ドメイン駆動プログラミングでは、ドメイン層のプログラミング実装が他の外部オブジェクトに依存することは許されません。ドメイン層のプログラミングは、ドメイン内のオブジェクトに対して私たちが持つ本来の機能と、現在のビジネスシナリオでどのような振る舞いを見せるかに基づいて行われます。これらの機能を十分に理解した後、直接コーディングできます。
たとえば、オブジェクト指向プログラミングに初めて触れた時によく遭遇する例として、鳥は飛べて犬は泳げるというものがあります。ビジネスドメインがこれらのオブジェクトの移動のみに関心がある場合、以下のような実装ができます。
ドメイン駆動プログラミングでは、このようにオブジェクトの機能を実装する必要があります(充血モデル)。MVC アーキテクチャでよく使われる貧血モデルのように、サービスにビジネスロジックを記述するのではありません。
もちろん、このようなプログラミング方法を採ったとしても、ドメイン駆動の実現にはまだ程遠く、一見単純な問題が大きな不安をもたらす可能性があります。たとえば、複雑なオブジェクトの初期化と永続化はどのように行うべきか。同じものが異なるドメインに存在するが焦点が異なる場合、そのオブジェクトをどのように抽象化すべきか。異なるドメインのオブジェクトが互いの情報を必要とする場合、どのように取得すべきか。
これらの問題について、コード例のセクションで参考となる解決策を提示していきます。
インフラストラクチャ層
インフラストラクチャ層は上記の各層に共通の技術機能を提供します。たとえば、モニタリングやメッセージ送信の機能、データベース・キャッシュ・NoSQL データベース・ファイルシステムの CRUD 機能などです。
2. まとめ
ドメイン駆動設計の各層をさらに分析すると、より具体的な階層構造は以下のようになります。
上記の階層化原則に基づき、前述の保険分野で参照できるコード構造は以下のようになります。各サブパッケージの概念と機能については、後続のコーディング例で詳しく説明します。
IV. ドメイン駆動開発コード
理論上、ドメイン層は他の層に依存せずビジネスのコアであるため、まずドメイン層のコードを記述すべきです。しかし、保険分野に関する知識が不足しているため、保険証券がどのような本来の機能を持っているかわからない可能性があります。そこで、ユースケースの力を借りてコードを直接示します。
1. ユースケース
ユーザーがフロントエンドページで保険商品を選択し、オプションの保障責任を選択し、申込者・被保険者の情報を入力し、決済方法(分割払い・一括払いなど)を選択して支払い後に保険申込を提出します。
サーバーが保険申込を受け付け → 引受 → 保険証券発行 → 保険証券給付の支払いを行います。
ここでユースケース 1 はユースケース 2 の前置ユースケースです。ユースケース 1 は既に正常に完了している(ユースケース 1 で保険料計算が完了している)と仮定し、ユースケース 2 のみを実装します。ユースケース 2 は大まかな実装にとどめ、コードスタイルを示すことだけを目的とします。
2. ユーザーインターフェース層のプログラミング実践
サブパッケージ構成
うち、client は insurance-client(公開二方パッケージ)の実装であり、web は REST スタイルのインターフェースの実装です。
ここで使用される入力パラメーターと戻り値のクラスはアプリケーション層で定義されています。
3. アプリケーション層のプログラミング実践
1. サブパッケージ構成
うち、最外層のインターフェースは特定のビジネスシナリオに対応しており、ビジネスの発展に応じてサブパッケージ分割できます。
pojo パッケージでは、アプリケーション層で使用されるさまざまなデータクラス(前述の IssuePolicyRequest など)と、他の層に伝播する際に型変換が必要なコンバーターを定義します。
tasks パッケージでは、一部のタイミングタスクのエントリーを定義します。
ドメインプログラミングの実践では大量の型変換が必要になる点に注意してください。MapStruct[2] などのフレームワークを使用して、これらの型変換がもたらす煩雑な作業を軽減できます。
2. ユースケースコード
ここのコードは、アプリケーション層がユースケース 2 をどのように処理するかを示しています。
ドメイン層のファクトリークラスを使用して Policy 集約を構築します。複雑なオブジェクトを渡す必要がある場合、型コンバーターを使用してアプリケーション層のデータクラスをドメイン層のエンティティクラスまたは値オブジェクトに変換する必要があります。
ドメイン層のサービスを使用して注文プロセスを制御します。
注文成功メッセージを送信すると、他のドメインは関心のあるメッセージを受信した際に反応します。
4. ドメイン層のプログラミング実践
1. サブパッケージ構成
ドメイン層には合計 5 つの一次サブパッケージがあります。
Anticorruption はドメイン腐敗防止層であり、現在のドメインが他のドメインや外部の情報を知る必要がある場合の、他ドメインの二方パッケージのカプセル化です。コードレベルの観点からは、腐敗防止層により、外部クライアントを呼び出す際のドメイン内での複雑なパラメーター組み立てや結果変換を回避できます。
factory は複雑な集約の初期化問題を解決します。ドメインモデルを外部呼び出し向けに設計しますが、外部もこのオブジェクトの組み立て方法を使用する必要がある場合、オブジェクトの内部構造を知る必要があります。これは呼び出し側の開発にとって非常に親切ではありません。さらに、複雑なオブジェクトや集約内のドメイン知識(ビジネスルール)は満たされる必要があります。外部が独自に複雑なオブジェクトや集約を組み立てることを許可すると、ドメイン知識が呼び出し側のコードに漏洩してしまいます。ここで注意すべきは、これは主に集約やエンティティに必要なデータを入力するものであり、オブジェクトの動作に関わるものではないという点です。
したがって、ここでのファクトリーの核心的な役割は、集約やエンティティの初期化に必要な外部データをあらゆる場所から取得することです。
model にはドメインオブジェクトの定義が含まれます。vo パッケージではドメイン内で使用される値オブジェクトを定義します。PolicyProduct という保険商品クラスがあるのがわかります。保険の分野では、保険証券に関連する特定の商品とそのスナップショット情報に関心を寄せます。そのため、ここに保険証券用の保険商品クラスを定義しています。腐敗防止層は、プロダクトドメインから取得した保険商品信息を、私たちが関心を持つ保険証券の保険商品クラスオブジェクトに変換する責務を持ちます。
ドメイン駆動設計のベストプラクティスによると、ドメインオブジェクトモデル内での services や repositories による外部情報の取得は許可されていません。その核心的な概念は、初期化が完了した完全なエンティティが何ができるか、またはどのような状態変化を経験した後に何が起こるかということです。
repository はストレージパッケージであり、ストレージインターフェースのみを定義し、具体的な実装には関与しません。具体的な実装はインフラストラクチャ層に委ねられます。これが依存性逆転の考え方です。
service はドメインサービスであり、ドメインオブジェクトに属さない動作のうち必要な操作、たとえば一部のプロセス制御などを定義します。
2. ユースケースコード
ここで policyRepository.save(policy); の行をコメントアウトしている点に注意してください。save と create を区別する理由は何でしょうか。
save はドメイン駆動設計において最も正しいアプローチです。集約やエンティティに変更があれば、ストレージはそれが新規か更新かを気にせず、ただ保存すればよいという考え方です。理屈上は美しいですが、リレーショナルデータベースのストレージには非常に不親切です。したがって、私たちのシナリオでは、本に書かれているいわゆるベストプラクティスに背く必要があります。リポジトリに対して作成か更新かを伝え、さらに更新の場合はどの列を更新するかまで指定します。
さらに、ドメイン駆動のベストプラクティスはイベント駆動に基づいており、Axon Framework はそれを完璧に実装しています。アプリケーション層が IssuePolicyCommand というコマンドを発行し、ドメイン層がそのコマンドを受け取ります。保険証券作成後に PolicyIssuedEvent を発行します。このイベントはモニタリングされ、イベントストアに永続化されます。現時点ではここでこの方法を実装する見込みは低いため、これ以上の紹介は行いません。
V. インフラストラクチャ層のプログラミング実践
1. サブパッケージ構成
ここではリポジトリの実装のみを示しますが、実際には RPC 呼び出し用の二方パッケージの実装クラス注入など多くの機能があります。前述の通り、ドメイン層はストレージの実装に関与せず、インフラストラクチャ層が担当します。インフラストラクチャ層は必要に応じてリレーショナルデータベース、キャッシュ、NoSQL を使用でき、ドメイン層はそれを認識しません。ここではリレーショナルデータベースを例にとります。dao と dataobject は mybatis generator などのツールで生成できます。Convertor はドメインオブジェクトと dataobject の間の変換を担当します。
2. ユースケースコード
Related Articles
-
A detailed explanation of Hadoop core architecture HDFS
Knowledge Base Team
-
What Does IOT Mean
Knowledge Base Team
-
6 Optional Technologies for Data Storage
Knowledge Base Team
-
What Is Blockchain Technology
Knowledge Base Team
Explore More Special Offers
-
Short Message Service(SMS) & Mail Service
50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00
