3

私は現在、ドメイン駆動設計 (DDD) と複数ドメインの統合シナリオに関する研究プロジェクトに取り組んでいます。

集約を検証するために別の BC に連絡する必要がある、境界付けられたコンテキストの 1 つでの使用例があります。実際、将来的に検証データを要求する BC がいくつかある可能性があります (ただし、現時点ではありません)。

現在、DDD強迫性障害神経衰弱で、パターンを正しく適用する方法が見つかりません(笑)。それについて人々からのフィードバックを本当に感謝します。


2 つの境界付けられたコンテキストについて。
- ユースケースが行われている最初のもの (BC_A) には、ユーザーに関連する要素のリストが含まれます。
- 外部のもの (BC_B) はそれらの要素についてある程度の知識を持っています

* したがって、BC_A から BC_B への検証要求は、BC_A からの集約のすべての要素のレビューを要求し、それらをどうするかについての仕様を含むレポートを返します。要素 (保持する必要があるかどうか、およびその理由)。
*集約の状態は、リクエストの後に「ドラフト」、「検証中」を通過し、返送されたレポートに応じて、「有効」または「has_error」(存在する場合)になります。ユーザーが後で仕様に従わないことを選択した場合、集計の状態が「制御」に渡される可能性があります。これは、何らかのエラーがあることを意味しますが、私たちはそれを処理しません。

コマンドはValidateMyAggregateCommandです

使用例は次のとおりです。

  1. id で対象の集計を取得する
  2. その状態を「検証中」に変更します
  3. 集計を永続化する
  4. 検証呼び出しを行う (別の BC へ)
  5. 検証レポートを永続化する
  6. ターゲット集計で検証レポートを確認します (結果に応じて状態が再び変化します。「OK」または「HAS_ERROR」である必要があります)。
  7. 集計を再度永続化する
  8. 検証結果に応じてドメイン イベントを生成する

これには 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) は正しいようですが、まだフィードバックが必要です。
それと国境を越える方法。

ありがとうございました!

4

1 に答える 1

4

集約を検証するために別の BC に連絡する必要がある、境界付けられたコンテキストの 1 つでの使用例があります。

それは本当に奇妙な問題です。通常、集約は有効か無効かは、それ自体の内部状態に完全に依存します。これが、より大きなウェブの単なるエンティティではなく、集約である理由です。

つまり、解決しようとしている実際の問題を完全に理解していないために、DDD パターンの適用に問題がある可能性があります。

余談ですが、で助けを求めるときは、問題を抽象化しようとするのではなく、実際の問題にできる限り忠実に従う必要があります。

そうは言っても、あなたを助けることができるいくつかのパターンがあります. Udi Dahan は信頼できるメッセージングに関する彼の講演で詳しく説明していますが、ここでは要点を取り上げます。

集約に対してコマンドを実行する場合、考慮すべき 2 つの側面があります。

  • 状態変化の持続
  • 副作用のスケジューリング

「副作用」には、他の集計に対して実行されるコマンドが含まれる場合があります。

あなたの例では、ハッピー パスに 3 つの異なるトランザクションが表示されます。

最初のトランザクションは、集計の状態を Validating に更新し、検証レポートを取得するタスクをスケジュールします。

このタスクは非同期で実行され、リモート ドメイン コンテキストにクエリを実行し、この BC でトランザクション #2 を開始します。これにより、検証レポートが保持され、2 番目のタスクがスケジュールされます。

検証レポートにコピーされたデータから作成された 2 番目のタスクは、トランザクション #3 を開始し、集計に対してコマンドを実行してその状態を更新します。このコマンドが終了すると、スケジュールするコマンドがなくなり、すべてが静かになります。

これは機能しますが、集計をプロセスに結びつけすぎる可能性があります。さらに、あなたのプロセスはバラバラです - 集合コードに散らばっており、ファーストクラスの市民として実際には認識されていません.

したがって、これが 2 つの追加のアイデアで実装される可能性が高くなります。まずはドメインイベントのご紹介。ドメイン イベントとは、特別な意味を持つ状態の変化を説明するものです。したがって、集約は変更 (ValidationExpired?) と、それを理解するために必要なローカル状態を記述し、イベントを非同期的に公開します。(つまり、任意のタスクを非同期で実行する代わりに、任意のドメイン イベントをペイロードとして PublishEvent タスクを非同期でスケジュールします)。

第二に、「プロセスマネージャー」の導入。プロセス マネージャーは、イベントをサブスクライブし、その内部ステート マシンを更新し、実行する (非同期) タスクをスケジュールします。(これらのタスクは、集約が以前にスケジュールしていたタスクと同じです)。プロセス マネージャーにはビジネス ルールがないことに注意してください。それらは集合体に属します。しかし、彼らは生成したドメイン イベントとコマンドを一致させる方法 (Gregor Hohpe による Enterprise Integration Patterns のメッセージングの章を参照) や、SLA 内で完了していないスケジュールされたタスクを検出するのに役立つタイムアウト タスクをスケジュールする方法などを知っています。

基本的に、プロセス マネージャーは集計に似ています。それら自体はドメイン モデルの一部ですが、それらへのアクセスはアプリケーション コンポーネントによって提供されます。集計では、コマンド ハンドラーはアプリケーションの一部です。コマンドが集約によって処理されると、非同期タスクをスケジュールするのはアプリケーションです。ドメイン イベントはイベント バス (インフラストラクチャ) に発行され、アプリケーションのイベント ハンドラーはそのバスにサブスクライブし、永続性を介してプロセス マネージャーを読み込み、処理するドメイン イベントを渡し、永続性コンポーネントを再度使用して更新されたプロセス マネージャーを保存します。その後、アプリケーションは保留中のタスクをスケジュールします。

私はそれが「強制された」ドメイン イベントであることを認識しています。ユーザーが要求したことはわかりましたが、実際に会話に現れることはありませんが、それでもドメイン イベントです。

私はそれを強制とは言いません。この検証プロセスの要件が実際にビジネスに由来する場合、ドメイン イベントはユビキタス言語に属するものです。

ドメイン イベントを宛先に送信する前に技術イベントに変換する必要がありますか

あなたがそれが何を意味すると思うか私にはわかりません。イベントは、起こったことを説明するメッセージです。「ドメインイベント」とは、ドメイン内で何かが起こったことを意味します。まだまだ発信中のメッセージです。

于 2016-07-07T05:47:50.843 に答える