イベントソーシング、CQRS、マイクロサービスを使用するシステムを設計しています。これは珍しいパターンではないことを理解しました。このサービスの主要な機能は、記録システムから再水和/復元する機能である必要があります。マイクロサービスは、MQ(Kafka)でコマンドとクエリを生成します。他のマイクロサービスが応答します(イベント)。コマンドとクエリは、監査と復元の目的でS3に保持されます。
現在のプロセスは、システムを復元するために、S3からイベントログを抽出し、単純にKafkaにフィードバックできるというものでした。
ただし、これは両方のプロデューサーandコンシューマーの経時的な変化を認めることができません。コマンド/クエリレベルでのバージョン管理は問題を解決するためにある程度の方法があるようですが、復元中にコマンドが受信されて処理されるときに、それがまったく同じであることを強制できるように、コンシューマのバージョン管理に集中できません。 [のバージョン]コマンドを初めて受信したときに処理を実行しているコード。
これを解決するために使用できるパターンはありますか?この機能を宣伝する他のシステムを知っている人はいますか?
編集:例を追加します。
「バイヤー」が私のオークションサイトの「セラー」に「質問」を送信します。フローは次のようになります。UI -> Web App: POST /question {:text text :to seller-id :from user-id} Web App -> MQ: SEND {:command send-question :args [text seller-id user-id]} MQ -< Audit: <command + args appended to log in S3> MQ -< Questions service: - Record question in DB - Email seller 'You have a question'
新しいビジネス要件の結果として、私は「質問サービス」コンシューマーを調整して、すべての未読の質問の数を永続化します。 DBスキーマが変更されます。これまで、売り手が質問を読んだかどうかはわかりませんでした。最終行は次のようになります。
MQ -< Questions service: - Record question in DB - Email seller 'You have a question' - Increment 'unread questions count'
変更前と変更後の2つのコマンドが問題です。 「未読の質問数」は1です。
システムがクラッシュします。 newコードを使用してコマンドを再生することで復元しました。復元の最後では、「未読の質問数」は2に等しくなります。この不自然な例では、結果は大惨事ではありませんが、復元された状態はnot以前の状態です。
まず、CommandsとEvents。の違いを理解して活用できるようにすることが重要です。
この質問 が簡潔に指摘しているように、コマンドは私たちが起こしたいことであり、イベントはすでに起こったこと。コマンドは、必ずしもシステムで重要なイベントを発生させるわけではありませんが、通常は発生します。たとえば、send message
コマンドは拒否される場合があります。その場合、イベントは発生しません(通常、この意味でエラーはイベントとは見なされませんが、診断ログに記録することはできます)。これで、send message
コマンドが受け入れられると、message sent
イベントが発生し、イベントの詳細で送信者、受信者、およびコンテンツを説明できるようになります。
システム状態について話すとき、実際にはコマンドの集まりではなく、イベントの集まりについて話し合っています。 イベントのみがシステムの状態の変化を引き起こす可能性があります。生活の例から引き出すために、地元のPublixスーパーマーケットに行ってフロリダの宝くじを購入するとしますチケット。コマンドは「チケットの購入」、イベントは「チケット発行」でした。次のコマンドは、パワーボールの数字を抽選するための宝くじです。宝くじは私の命令を無視します(しかし、私には知識がありません)。そして、イベント "PowerBall番号が選択されました"は私の希望に関係なく行われます。私の数が一致する場合、「ジャックポットが勝った」というイベントが私に起こります(そして、私の命令が聞こえたと思います)。そうでない場合、自分のコマンドが無視されたことがわかります。
歴史的な観点から見ると、宝くじはイベントのサブセットにのみ関心があります。宝くじは、(a)チケットが発行されたこと、(b)番号が選択されたこと、および(c)ジャックポットが当たったことのみを気にします。それらは興味のあるアイテムです。チケットを購入したり、当選したいなどの行為は、関係ありません。私が負けた後のチケットの場合も同様です。現実の世界はありふれたイベントで変化しますが、システムにとって重要なイベントのみを記録する必要があります。
理論的には、イベントソーシングテクニックでは、イベントのストリームを最初から再生して現在の状態に到達させることができます。これは、根本的なシステム条件が一定で決定論的であるという仮定に依存しています。ただし、これらの仮定は多くのシステムで有効ではありません。イベントに関連するデータや、関心のあるイベントの種類は、コンピュータソフトウェアの進化に伴って変化する可能性があります。さらに、すべてのクエリに応じて現在の状態を再計算すると、計算コストが高くなる可能性があります。このため、システム状態のスナップショットは、既知の時点を表すために取られることが多く、最新のイベントを追加できます。
イベントストリームを複数のバージョンにまたがって再生することは依然として可能ですが、これを行うために必要な人間の労力は、コストが非常に高くなる可能性があります。その機能をシステムに設計する正当な理由がない限り、スナップショットを利用するようにシステムを構築する方がよいでしょう。
質問の例
質問の例では、アーキテクチャは本当にイベントベースではありません。コマンドベースです。コマンドを再生すると、システム状態が作成されます。これはアンチパターンであり、修正する必要があります。代わりに、主なイベントは次のとおりです。
これらの各イベントは、現在の状態を示すために「再生」できます。たとえば、質問する際のシステムの動作は、販売者にメールを送信してunanswered question
カウンターをインクリメントすることです。この動作は変更できます。しかし、質問が出されたという事実はそうではありません。同様に、売り手が応答すると、システムはunanswered question
カウンターをデクリメントする可能性があります。この動作は変更可能ですが、売り手が応答したという事実は変わりません。
ほとんどのイベントソーシングシステムは、クエリに応答して特定のイベントストリームを再生することにより、未回答の質問の数を動的に計算します。
コマンドとクエリは、監査と復元の目的でS3に保持されます。
監査については、確かに。 復元の場合?それは奇妙で、頭痛の種になるでしょう。
イベントソーシングを行う場合は、コマンドではなく、イベント(過去に発生したこと)から状態を復元する必要があります。これにより、コマンド実装の変更に関連する問題のほとんどを回避できます。永続的な状態変更に対処するだけで済みます。
バージョン管理はまだ問題です。特に、永続化されたイベントができるだけしなやかであることを確認する必要があります(ドメイン内の概念を直接シリアル化するのではなく、DTO表現)。ストアからイベントを読み取るとき、それらを再水和状態に適用する前に、必要に応じてイベントを更新する機会があります。