私は現在、ドメイン駆動設計 (DDD) と複数ドメインの統合シナリオに関する研究プロジェクトに取り組んでいます。
集約を検証するために別の BC に連絡する必要がある、境界付けられたコンテキストの 1 つでの使用例があります。実際、将来的に検証データを要求する BC がいくつかある可能性があります (ただし、現時点ではありません)。
現在、DDD強迫性障害神経衰弱で、パターンを正しく適用する方法が見つかりません(笑)。それについて人々からのフィードバックを本当に感謝します。
2 つの境界付けられたコンテキストについて。
- ユースケースが行われている最初のもの (BC_A) には、ユーザーに関連する要素のリストが含まれます。
- 外部のもの (BC_B) はそれらの要素についてある程度の知識を持っています
* したがって、BC_A から BC_B への検証要求は、BC_A からの集約のすべての要素のレビューを要求し、それらをどうするかについての仕様を含むレポートを返します。要素 (保持する必要があるかどうか、およびその理由)。
*集約の状態は、リクエストの後に「ドラフト」、「検証中」を通過し、返送されたレポートに応じて、「有効」または「has_error」(存在する場合)になります。ユーザーが後で仕様に従わないことを選択した場合、集計の状態が「制御」に渡される可能性があります。これは、何らかのエラーがあることを意味しますが、私たちはそれを処理しません。
コマンドはValidateMyAggregateCommandです
使用例は次のとおりです。
- id で対象の集計を取得する
- その状態を「検証中」に変更します
- 集計を永続化する
- 検証呼び出しを行う (別の BC へ)
- 検証レポートを永続化する
- ターゲット集計で検証レポートを確認します (結果に応じて状態が再び変化します。「OK」または「HAS_ERROR」である必要があります)。
- 集計を再度永続化する
- 検証結果に応じてドメイン イベントを生成する
これには 8 つのステップが含まれており、1 ~ 3 トランザクションまたはそれ以上のトランザクションが含まれている可能性があります。
(UI でアクセスするために) 検証レポートのローカルを永続化する必要があり、それができると思います。
- 検証呼び出しが独立した後 (レポートは独自の集計です)
- ターゲット集約を永続化するとき(その中にあります)
私は最初のオプション (ステップ 5) を好みます。これは、より分離されているためです - ここに不変条件があると主張できたとしても (???) - レポートの永続性と、集計。
私は実際に呼び出し自体に苦労しています (ステップ 4)。
私はいくつかの方法でそれを行うことができると思います:
- A. REST 実装による同期 RPC 呼び出し
- B. 応答なしの呼び出し (無効) (ファイア アンド フォーゲット) テーブルでいくつかの実装オプションを許可する (同期/非同期)
- C. 他の BC に到達するために技術的なイベントに変換されたドメイン イベント
A. 同期 RPC 呼び出し
// code_fragment_a
// = ValidateMyAggregateCommandHandler
// ---
myAggregate = myAggregateRepository.find(command.myAggregateId()); // #1
myAggregate.changeStateTo(VALIDATING); // #2
myAggregateRepository.save(myAggregate); // #3
ValidationReport report = validationService.validate(myAggregate); // #4
validationReportRepository.save(report); // #5
myAggregate.acknowledge(report); // #6
myAggregateRepository.save(myAggregate); // #7
// ---
これvalidationService
は、REST サービス Bean を使用してインフラストラクチャ レイヤーに実装されたドメイン サービスです (ローカル検証の可能性もありますが、私のシナリオではそうではありません)。
呼び出しにはすぐに応答が必要であり、呼び出し元 (コマンド ハンドラー) は応答が返されるまでブロックされます。そのため、高い時間結合が導入されます。
技術的な理由で検証呼び出しが失敗した場合、例外が発生し、すべてをロールバックする必要があります。コマンドは後で再生する必要があります。
B. 応答なしの呼び出し (同期または非同期)
このバージョンでは、コマンド ハンドラーは集約の「検証中」状態を保持し、検証要求を起動 (および忘れ) します。
// code_fragment_b0
// = ValidateMyAggregateCommandHandler
// ---
myAggregate = myAggregateRepository.find(command.myAggregateId()); // #1
myAggregate.changeStateTo(VALIDATING); // #2
myAggregateRepository.save(myAggregate); // #3
validationRequestService.requestValidation(myAggregate); // #4
// ---
ここで、レポートの確認は、初期トランザクションの内部または外部で、同期または非同期の方法で発生する可能性があります。
上記のコードを専用のトランザクションで使用すると、検証呼び出しでの失敗を無害にすることができます (impl に再試行メカニズムがある場合)。
このソリューションにより、同期通信をすばやく簡単に開始し、後で非同期通信に切り替えることができます。だから柔軟です。
B.1. 同期実装
この場合、(インフラストラクチャ層の) validationRequestService の実装は、直接の要求/応答を行います。
// code_fragment_b1_a
// = SynchronousValidationRequestService
// ---
private ValidationCaller validationCaller;
public void requestValidation(MyAggregate myAggregate) {
ValidationReport report = validationCaller.validate(myAggregate);
validationReportRepository.save(report);
DomainEventPublisher.publish(new ValidationReportReceived(report))
}
// ---
レポートは専用のトランザクションで永続化され、イベントを発行すると、集計で実際の確認作業を行う 3 番目のコード フラグメント (アプリケーション レイヤー内) がアクティブ化されます。
// code_fragment_b1_b
// = ValidationReportReceivedEventHandler
// ---
public void when(ValidationReportReceived event) {
MyAggregate myAggregate = myAggregateRepository.find(event.targetAggregateId());
ValidationReport report = ValidationReportRepository.find(event.reportId());
myAggregate.acknowledge(report);
myAggregateRepository.save(myAggregate);
}
// ---
ここでは、インフラ層からアプリ層へのイベントがあります。
B.2. 非同期
非同期バージョンは、ValidationRequestService impl (code_fragment_b1_a) の以前のソリューションを変更します。JMS/AMQP Bean を使用すると、最初にメッセージを送信し、後で個別に応答を受信できます。
メッセージング リスナーが同じ ValidationReportReceived イベントを発生させ、残りのコードは code_fragment_b1_b と同じになると思います。
この投稿を書いているとき、このソリューション (B2) は、ネットワーク通信に関してより分離され、信頼性が高いため、交換の対称性が向上し、技術的な点が改善されていることに気付きました。現時点では、それほど複雑ではありません。
C. BC 間のドメイン イベントとバス
最後の実装では、ドメイン サービスを使用して他の BC から検証を要求する代わりに、MyAggregateValidationRequested のようなドメイン イベントを発生させます。私はそれが「強制された」ドメイン イベントであることを認識しています。ユーザーが要求したことはわかりましたが、実際に会話に現れることはありませんが、それでもドメイン イベントです。
問題は、イベント ハンドラーをどこに、どのように配置するか、まだわかりません。インフラストラクチャ ハンドラはそれを直接取得する必要がありますか?
ドメインイベントを宛先に送信する前に技術イベントに変換する必要がありますか???
データ構造である場合のある種の DTO のような技術的イベント
メッセージングに関連するすべてのコードは、システム間の通信にのみ使用されるため、インフラストラクチャ レイヤー (ポート/アダプター スロット) に属していると思います。
そして、これらのパイプ内で発生/処理コードとともに転送される技術イベントは、コマンドと同様にシステム状態の変化につながるため、アプリケーション層に属している必要があります。それらはドメインを調整し、インフラによって起動されます (アプリケーション サービスを起動するコントローラーのように)。
コマンド内のイベントの変換に関するいくつかの解決策を読みましたが、システムがより複雑になり、メリットがないと思います。
したがって、私のアプリケーション ファサードは 3 種類の相互作用を公開します: - コマンド - クエリ - イベント
この分離により、コマンドを UI から、イベントを他の BC からより明確に分離できると思います。
わかりました、投稿がかなり長く、少し面倒かもしれませんが、これは私が立ち往生しているところです。何か助けになることがあれば、事前に感謝します.
私の問題は、2 BC の統合に苦労していることです。
さまざまな解決策:
- サービス RPC (#A) は単純ですが、規模が限られています。 -
メッセージングを伴うサービス (#B) は正しいようですが、まだフィードバックが必要です。
それと国境を越える方法。
ありがとうございました!