web-dev-qa-db-ja.com

CQRS-Event Sourcing:読み取りモデル内の予期された順序でイベントを処理する方法

データの整合性を確保するために最善を尽くすために、CQRSイベントソーシングアプリケーションで読み取りモデルを処理する方法についての洞察を探しています。

不足している点は読み取りモデル内のプロジェクションが書き込みモデルから作成されたイベントを期待される順序(つまり、書き込みモデルがイベントを生成しました)

例でシナリオを明確にしてみましょう。

newsと呼ばれる集約のイベントを生成する書き込みスタックを備えたアプリと、単一のプロジェクションによって作成された読み取りスタックがあるとします。これは基本的に、いくつかのプロパティ(たとえば、タイトル、要約、著者名など)。
このようにして、クライアントアプリは、編集者が公開した最新のニュースをすべてリストするユーザーインターフェイスをレンダリングできます。

イベントソースのインフラストラクチャは、イベントをドキュメントとしてMongoDBコレクション内に保存し、対応するメッセージをサービスバスに発行するイベントストアです。これにより、クラシックpub-subパターンプロジェクションはメッセージをサブスクライブし、それに応じて適切な作業を行うことができます。
RabbitMQに精通している人にとって、この種のものはfanout exchangeを使用して実装できます。 Azureサービスバスでは、代わりにtopicを使用できます。
この種のシナリオでは、書き込みモデルと読み取りモデルは、書き込みスタックと読み取りスタックの両方の要求負荷に基づいて、互いに独立して自由にスケーリングできる異なるマシンにデプロイされます。

このようなシナリオの場合、特定の時点で、システムの読み取りモデルをホストしているアプリのインスタンスが2つ以上あることは完全に可能です。これらのインスタンスは、書き込みモデルによって発行されたイベントに対して競合するコンシューマーになります。

短い時間間隔で2つのイベント(E1とE2など)がサービスバスに発行され、読み取りモデルをホストしているアプリの2つの実行中のインスタンス(M1とM2など)があるとします。
これらのシナリオを考えると、イベントE1がマシンM1によって処理され、イベントE2がマシンM2によって同時に処理されることは完全に可能です(2つのインスタンスが競合)であることを思い出してくださいサービスバスに発行されたメッセージを介して)。この時点で投影の最終状態は予測不可能です。これは、各インスタンスが他のインスタンスよりも高速である可能性があるためです。

典型的な例は、両方のイベントのタイプがTitleSetの場合です。これは、編集者が非常に短い時間間隔でニュースタイトルを2回変更することを決定したためです。処理の終わりに、予測に誤ったものが含まれます。ニュースのタイトルと間違ったタイトルは、クライアントアプリケーションの最終ユーザーに表示されます(もちろん、プロジェクションからユーザーインターフェイスのデータを取得します)。

アプリケーションの読み取りスタックで可能な限り最高の一貫性が保証されるように、このようなシナリオを処理する最良の方法は何ですか?

PS:イベントE1とE2は、書き込みモデルによって予期された順序で生成され、イベントストア内に適切に格納されています。 私たちが話しているデータの不整合は、読み取りモデルのみです

6
Enrico Massone

イベントソースのインフラストラクチャは、イベントをドキュメントとしてMongoDBコレクション内に保存し、対応するメッセージをサービスバスに発行するイベントストアです。これにより、で従来のpub-subパターン関係するすべてのプロジェクションがメッセージをサブスクライブし、それに応じて適切な作業を行うことができます。

(エンファシスを追加)

ここでのこの仮定は、プッシュバックしたいものです。 Pub/Subは、単一のメッセージのみを個別に処理するコンシューマ向けに機能します。状態を必要とする消費者は、イベントではなく履歴を消費する必要があります。

避けられないほど最適化されていないケースでは、コンシューマーは実行されるたびに順序付けられたイベントの履歴全体を読み取り、それらすべてを処理します。

これの最適化されたバージョンは、消費者がイベント履歴のどこで中断したかを追跡し、「イベントX以降のすべてのイベント」クエリを実行して何が起こったかを確認することです。取り返しのつかないほど最適化されていないケースは、単に「イベントがなかったため、すべてのイベント」というこの退化したケースです。

読み取りモデルを再構築するためではなく、前述のようにコンシューマーを起動して履歴をプルするために、pub/subパターンが適用されたままになる場合があります(実際には、レイテンシ削減メカニズムになります)。

受信したイベントがすでに知られているもののすぐ後継であることを認識するために少し賢いことも消費者に問題はありません。これは通常、履歴にその位置を示すメタデータをイベントに添付して実行されます。

したがって、競合する消費者のシナリオでは、2つの異なる読み取り動作が動作しているのを目にすることがあります。 E1の受信側は、E1が前の状態の直後の継承者であると判断し、単に動作します。 E2の受信側は、メタデータから少なくとも1つのイベントが欠落していることを認識しているため、イベントストリームのコピーを更新し、シーケンス[E1、E2]を受け取ってそれを消費します。

いくつかの参考文献

DDD Europeの会議で、私が話しているスピーカーは、可能な限りPub/Subを避けているところに気づきました。 - レイモンド・ルジェス、2016

グレッグ・ヤング、 Polyglot Data(2014) ;グレッグはプルの利点について少し話しています。

6
VoiceOfUnreason