web-dev-qa-db-ja.com

ES / CQRS並行処理

仕事でそれを適用する必要があるかもしれないので、私は最近CQRS/ESに飛び込み始めました。それは多くの問題を解決するので、私たちの場合、それは非常に有望なようです。

私は、ES/CQRSアプリが単純化された銀行のユースケース(出金)にコンテキスト化されたように見える方法についての大まかな理解をスケッチしました。

ES / CQRS

要約すると、Aさんがお金を引き出す場合:

  • コマンドが発行された
  • コマンドは検証/検証のために渡されます
  • 検証が成功した場合、イベントはイベントストアにプッシュされます。
  • アグリゲーターがイベントをデキューして、アグリゲートに変更を適用します

私が理解したところによると、イベントログはFACTSのログであるため、真実の情報源です。それから、そこからすべての予測を導き出すことができます。


さて、この壮大な計画において私が理解していないのは、この場合に何が起こるかです:

  • ルール:残高をマイナスにすることはできません
  • 人物Aの残高は100e
  • 人物Aが100eのWithdrawCommandを発行する
  • 検証に合格し、100eイベントのMoneyWithdrewEventが発行されます
  • その間に、Aは100eの別のWithdrawCommandを発行します。
  • 最初のMoneyWithdrewEventはまだ集計されていないため、検証は合格です。これは、検証が(まだ更新されていない)集計に対して行われるためです。
  • 100eのMoneyWithdrewEventが別のタイミングで発行されます

==>バランスが-100eの状態になり、ログに2 MoneyWithdrewEventが含まれています

私が理解しているように、この問題に対処するためのいくつかの戦略があります:

  • a)集約バージョンIDをイベントと共にイベントストアに配置します。変更時にバージョンの不一致がある場合は、何も起こりません。
  • b)いくつかのロック戦略を使用します。これは、検証層が何らかの方法で作成する必要があることを意味します

戦略に関する質問:

  • a)この場合、イベントログは真実のソースではなくなりました。対処方法は?また、撤回を許可することは完全に間違っていましたが、この場合はロックを使用する方が良いですか?
  • b)ロック==デッドロック、ベストプラクティスに関する洞察はありますか?

全体的に、私の理解は同時実行性の処理方法について正しいですか?

注:同じ人がそのような短い時間枠で2倍のお金を引き出すことは不可能であることを理解していますが、詳細に迷わないように、簡単な例を取り上げました

21
Louis F.

私は、ES/CQRSアプリが単純化された銀行のユースケース(出金)にコンテキスト化されたように見える方法についての大まかな理解をスケッチしました。

これは、イベントソースアプリケーションの完璧な例です。はじめましょう。

毎回コマンドが処理または再試行されます(理解しますが、しばらくお待ちください)。次の手順が実行されます。

  1. コマンドがコマンドハンドラー、つまり_Application layer_のサービスに到達します。
  2. コマンドハンドラーはAggregateを識別し、リポジトリからロードします(この場合、ロードはnew-Aggregateインスタンスを実行して、この集約の以前に発行されたすべてのイベントをフェッチし、集約自体に再適用します。集約バージョン後で使用するために保存されます。イベントが適用された後、Aggregateは最終状態になります(つまり、当座預金残高は数値として計算されます)
  3. コマンドハンドラは、Account::withdrawMoney(100)のようにAggregateの適切なメソッドを呼び出し、生成されたイベントを収集します。つまり、MoneyWithdrewEvent(AccountId, 100);アカウントに十分なお金がない場合(残高<100)、例外が発生し、すべてが中止されます。それ以外の場合は、次のステップが実行されます。
  4. コマンドハンドラは、Aggregateをリ​​ポジトリに永続化しようとします(この場合、リポジトリは_Event Store_です)。これは、versionAggregateAggregateがロードされたときのものである場合に限り、_Event stream_に新しいイベントを追加することによって行われます。バージョンが同じでない場合、その後、コマンドが再試行されます-ステップ1に進みますversionが同じ場合、イベントは_Event stream_に追加され、クライアントにはSuccessステータスが提供されます。

このバージョンチェックは楽観的ロックと呼ばれ、一般的なロックメカニズムです。他の1つのメカニズムは、悲観的ロックで、現在の書き込みが完了するまで他の書き込みが(開始されていないように)ブロックされます。

_Event stream_という用語は、同じAggregateによって発行されたすべてのイベントを抽象化したものです。

_Event store_は、最終状態だけでなく、Aggregateへのすべての変更が格納される永続化の一種であることを理解する必要があります。

a)この場合、イベントログは真実のソースではなくなりました。対処方法は?また、撤回を許可することは完全に間違っていましたが、この場合はロックを使用する方が良いですか?

イベントストアは常に真実の源です。

b)ロック==デッドロック、ベストプラクティスに関する洞察はありますか?

楽観的ロックを使用すると、ロックがなく、コマンドを再試行するだけです。

とにかく、ロック!=デッドロック

22

私は、ES/CQRSアプリが単純化された銀行のユースケース(出金)にコンテキスト化されたように見える方法についての大まかな理解をスケッチしました。

閉じる。問題は、「集計」を更新するロジックが奇妙な場所にあることです。

より一般的な実装では、コマンドハンドラーがメモリに保持するデータモデルと、イベントストア内のイベントのストリームの同期が維持されます。

説明する簡単な例は、コマンドハンドラがイベントストアへの同期書き込みを行い、イベントストアへの接続が書き込みの成功を示している場合にモデルのローカルコピーを更新する場合です。

コマンドハンドラーがイベントストアと再同期する必要がある場合(その内部モデルがストアのモデルと一致しないため)、ストアから履歴を読み込み、独自の内部状態を再構築することでそれを行います。

つまり、矢印2と3(存在する場合)は通常、集約ストアではなくイベントストアに接続されます。

集約バージョンIDをイベントと共にイベントストアに配置します。変更時にバージョンの不一致がある場合、何も起こりません

これのバリエーションは通常、イベントストリームのストリームにappendingではなく、通常、[〜#〜] put [〜#〜]特定のストリーム内の場所。その操作がストアの状態と互換性がない場合、書き込みは失敗し、サービスは適切な失敗モード(クライアントへの失敗、再試行、マージなど)を選択できます。べき等書き込みを使用すると、分散メッセージングの多くの問題が解決しますが、当然、べき等書き込みをサポートするストアが必要です。

1
VoiceOfUnreason