7

CQRS + イベント ソーシング アーキテクチャを実装するさまざまなサンプル アプリケーションとフレームワークがあり、ほとんどの場合、イベント ハンドラーを使用して、イベント ストアに格納されたドメイン イベントから非正規化ビューを作成する方法について説明しています。

このアーキテクチャをホストする 1 つの例は、書き込み側へのコマンドを受け入れ、非正規化されたビューのクエリをサポートする Web API です。この Web API は、負荷分散されたファーム内の多くのマシンにスケールアウトされている可能性があります。

私の質問は、読み取りモデルのイベント ハンドラーがホストされている場所です。

考えられるシナリオ:

  1. 別のホスト上の単一の Windows サービスでホストされます。 もしそうなら、それは単一障害点を作成しませんか? これも展開を複雑にする可能性がありますが、単一スレッドの実行が保証されます。欠点は、読み取りモデルでレイテンシが増加する可能性があることです。

  2. Web API 自体の一部としてホストされます。たとえば、イベント ストレージとイベント サブスクリプションの処理にEventStore を使用している場合、単一のイベントごとに複数のハンドラー (各 Web ファーム プロセスに 1 つ) が起動されるため、ハンドラーが読み取り/書き込みを試みるときに競合が発生します。彼らの読み取りストアに?それとも、特定の集約インスタンスについて、そのすべてのイベントがイベント バージョン順に一度に 1 つずつ処理されることが保証されていますか?

展開を簡素化し、イベントをリッスンする必要があるプロセス マネージャーもサポートするため、シナリオ 2 に傾倒しています。1 つのイベント ハンドラーのみが 1 つのイベントを処理する必要があるため、同じ状況です。

EventStore はこのシナリオを処理できますか? 最終的に一貫性のあるアーキテクチャで、他の人はイベントの処理をどのように処理していますか?

編集:

明確にするために、CQRS の「Q」のテーブルを読み取るのではなく、非正規化されたテーブルにイベント データを抽出するプロセスについて話しているのです。

私が探しているのは、イベントの処理が冪等の方法で処理されると仮定して、冗長性とスケールをサポートできる読み取りモデル/サガ/などのイベント処理をどのように実装および展開するかのオプションだと思います。

イベント ストアにイベントとして保存されたデータを処理するための 2 つの解決策を読みましたが、どちらを使用すべきかわかりません。

イベントバス

イベント バス/キューは、通常はリポジトリの実装によって、イベントが保存された後にメッセージを公開するために使用されます。読み取りモデルやサガ/プロセス マネージャーなどの利害関係者 (サブスクライバー) は、"何らかの方法で" バス/キューを使用して、べき等な方法で処理します。

キューが pub/sub である場合、これは、各ダウンストリームの依存関係 (読み取りモデル、saga など) が、キューをサブスクライブするためにそれぞれ 1 つのプロセスしかサポートできないことを意味します。複数のプロセスは、それぞれが同じイベントを処理し、下流で変更を行うために競合することを意味します。べき等処理は、一貫性/同時実行性の問題に対処する必要があります。

キューがコンシューマーと競合する場合、少なくとも、冗長性のために各 Web ファーム ノードでサブスクライバーをホストする可能性があります。ただし、これにはダウンストリームの依存関係ごとにキューが必要です。サガ/プロセス マネージャー用に 1 つ、読み取りモデルごとに 1 つなど、結果整合性のためにリポジトリはそれぞれに公開する必要があります。

購読/フィード

利害関係者 (サブスクライバー) がオンデマンドでイベント ストリームを読み取り、読み取りモデルに処理するために既知のチェックポイントからイベントを取得するサブスクリプション/フィード。

これは、必要に応じて読み取りモデルを再作成するのに最適です。ただし、通常の pub/sub パターンでは、ダウンストリームの依存関係ごとに 1 つのサブスクライバー プロセスのみを使用する必要があるように思われます。たとえば、各 Web ファーム ノードに 1 つずつ、同じイベント ストリームに複数のサブスクライバーを登録すると、それらはすべて、同じそれぞれの読み取りモデルを処理および更新しようとします。

4

2 に答える 2

7

私たちのプロジェクトでは、サブスクリプション ベースのプロジェクションを使用します。その理由は次のとおりです。

  • 書き込み側へのコミットはトランザクションである必要があり、2 つのインフラストラクチャ (イベント ストアとメッセージ バス) を使用する場合は、DTC の使用を開始する必要があります。そうしないと、イベントがストアに保存されてもバスに発行されないリスクがあります。実装に応じて、またはその逆です。DTC と 2 フェーズ コミットは厄介なものであり、この方法は使用したくない
  • イベントは通常、メッセージ バスでパブリッシュされます (これもサブスクリプションを介して行います)。これにより、異なる境界付けられたコンテキスト間のイベント ドリブン通信が行われます。メッセージ サブスクライバーを使用して読み取りモデルを更新する場合、読み取りモデルを再構築することを決定すると、他のサブスクライバーもこれらのメッセージを受け取り、システムが無効な状態になります。パブリッシュされたメッセージの種類ごとにサブスクライバーを 1 つだけ持つ必要があると述べたときに、既にこのことについて考えたことがあると思います。
  • メッセージ バス コンシューマーはメッセージの順序を保証しないため、読み取りモデルが混乱する可能性があります。
  • 通常、メッセージ コンシューマーはメッセージをキューに送り返すことで再試行を処理し、通常はキューの最後まで再試行します。これは、イベントが大幅に狂ってしまう可能性があることを意味します。さらに、通常、何回か再試行した後、メッセージ コンシューマーは有害なメッセージを放棄し、それを何らかの DLQ に配置します。これがあなたの予測である場合、これは、1 つの更新が無視され、他の更新が処理されることを意味します。これは、読み取りモデルが一貫性のない (無効な) 状態になることを意味します。

これらの理由を考慮して、何でもできるシングルスレッドのサブスクリプションベースのプロジェクションがあります。キャッチアップ サブスクリプションを使用してイベント ストアにサブスクライブし、独自のチェックポイントでさまざまなタイプのプロジェクションを実行できます。簡単にするために、他の多くのものと同じプロセスでそれらをホストしますが、このプロセスは 1 台のマシンでのみ実行されます。このプロセスをスケールアウトしたい場合は、サブスクリプション/プロジェクションを取り除く必要があります。この部分は、アセンブリとして共有できる読み取りモデル DTO 自体を除いて、他のモジュールへの依存関係が事実上ないため、簡単に実行できます。

サブスクリプションを使用すると、既にコミットされているイベントを常に投影できます。プロジェクションで何か問題が発生した場合、書き込み側は間違いなく真実のソースであり、そのままであり、プロジェクションを修正して再度実行するだけです。

2 つの別個のものがあります。1 つは読み取りモデルに投影するためのもので、もう 1 つはイベントをメッセージ バスにパブリッシュするためのものです。この構成は非常にうまく機能することが証明されています。

于 2016-11-26T16:38:08.603 に答える