質問への回答
CQRS(コマンドクエリ責任分離)パターンは、書き込み最適化されたモデル(PostgreSQL、Oracle)を読み取り最適化されたプロジェクション(Elasticsearch、MongoDB)から分離することによって、高い読み取りシナリオにおけるスケーラビリティのボトルネックを解決するためにドメイン駆動設計の実践から生まれました。このアーキテクチャの二分化は、コマンドの永続性とクエリの可用性との間に固有の時間的ギャップを生じさせます。非同期イベントプロセッサは、読み取るモデルが状態変更を反映する前に、ネットワーク境界を超えてデータを非正規化する必要があります。
これらのシステムを自動化する際の根本的な問題は、テスト実行スレッドとバックグラウンドプロジェクションワーカー間のレースコンディションにあります。コマンドを送信した後すぐに読み取りモデルに対してアサーションを行うと、処理の遅延により予測できない失敗が発生します。従来の解決策は、恣意的な遅延や素朴なポーリングに依存しており、これによりパイプラインは受け入れられないほどスローダウンするか、インフラストラクチャストレス下での偽陰性を生じます。
堅牢な解決策は、ストリームオフセットや変更データキャプチャトークン(Debezium、Kafkaのコンシューマグループ)を使用したイベント位置の追跡を実装し、決定論的な同期バリアを確立します。テストフレームワークは、最後に発行されたドメインイベントの位置をキャプチャし、その特定の位置が消費されたことを確認するまで読み取りモデルのメタデータをポーリングします。指数バックオフを利用し、回路ブレーカのタイムアウトを執り入れることで、無限ブロックを防ぎつつ、サブ秒の整合性精度を維持します。
実生活の状況
高頻度取引プラットフォームのテスト自動化を設計している際、私たちのチームは、取引の永続性にPostgreSQLを使用し、リアルタイムのバランスクエリにElasticsearchを使ったポートフォリオ評価テストで重要な不安定性に直面しました。購入/販売コマンドを実行し、ポートフォリオエンドポイントを即座にクエリすると、Kafka Connectのプロジェクションが更新をインデックスするのに300〜800msを要するため、古いトランザクション前のバランスが取得され、CIビルドの35%が誤って失敗しました。
私たちの最初の解決策は、すべての書き込み操作の後に固定のThread.sleep(2000)ステートメントを挿入し、アサーションの前にElasticsearchのインデックスの完了を保証することでした。このアプローチは一時的に結果を安定させましたが、スイートの実行時間が400%増加し、ハードウェア性能への時間依存性に弱く、時折固定の遅延を超えるガーベジコレクションの停止やネットワークの混雑に脆弱なままでした。
2つ目の評価されたアプローチは、クエリエンドポイントでの指数バックオフを用いた一般的なポーリングを実装し、期待される値が現れるまでアサーションを再試行しました。この方法は固定スリープよりも優れていますが、「まだ更新されていない」と「不正確な値」の状態の間の曖昧さがあり、同時に同じ集約を変更する複数のテストシナリオの管理ができず、クロステストの汚染や偽陽性を引き起こす可能性がありました。
最終的に、プロジェクション層の計測を組み込んで、Elasticsearchのドキュメントメタデータ内で最後に処理されたKafkaオフセットを公開する第3のアプローチを選択しました。私たちのテストハーネスは、コマンド発行イベントのオフセットをキャプチャし、そのメタデータがそのオフセットを消費したことを示すまで読み取りモデルをポーリングする専門の待機ユーティリティを使用しました。これにより、時間的な推測なしに一貫性が保証されました。これにより、平均テスト実行時間が52秒から14秒に短縮され、非決定的な負のテストが完全に排除され、非同期の不確実性が決定論的な同期ポイントに変換されました。
候補者が見逃しがちな点
複数の並行CIランナーが同時に読み取りモデルプロジェクションを共有する集約を更新する際に、CQRSの非同期性を損なうロックメカニズムを導入せずにテストデータの干渉を防ぐ方法は?
回答:UUIDでサフィックスされた集約識別子と、イベントメタデータに埋め込まれたテストラン相関IDを使用して論理テナントアイソレーションを実装します。リードモデルのインデックスを、テストラン識別子をルーティングキーまたはフィルターパラメータとして含むように構成し、プロジェクションクエリが特定のテスト実行コンテキストに関連するドキュメントのみを返すことを保証します。これにより、物理データベースロックを使用せずに並行テスト実行が可能になり、同時にパイプラインインスタンス間で厳格なデータの分離が保たれます。
CQRSにおける書き込みモデルの動作と読み取りモデルの動作を検証する際の根本的なアーキテクチャの違いは何であり、この違いがなぜ異なるアサーション戦略を必要とするのか?
回答:書き込みモデルの検証は、トランザクションの原子性、ビジネス不変の強制、およびドメインイベントの発行の正確性に焦点を当てており、通常はテストアイソレーションを維持するためにデータベーストランザクションのロールバック機能を利用します。読み取りモデルの検証は、非正規化の精度、クエリ応答時間のSLA、および最終的整合性ウィンドウのコンプライアンスに関心があり、非同期処理の遅延を考慮し、プロジェクションが重複イベントや順不同の配信を冪等に処理できることを確認するアサーションが必要です。
特にプロジェクションが楽観的同時実行制御を実装している場合、データ整合性を損なうことなく、読み取りモデルが順不同のイベント配信や重複イベント処理を正しく処理できることを検証するために、どのように自動化テストを設計しますか?
回答:故意にイベントを順番を間違えて公開する障害注入テストハーネスを構築し、Kafkaのパーティションの再割り当てやタイムスタンプの操作を利用して、次に、読み取りモデルがベクタークロックを使用してイベントをキューに入れて再順序付けするか、集約バージョン番号に基づいて冪等の更新を適用することを確認します。プロジェクションが単調整合性を維持することを確認するために、シーケンス番号が決して減少せず、返送されたイベント(手動のオフセットリセットによりシミュレーションされた)が幻のレコードを作成したり、クエリストア内でカウンターを複数回増加させないことを確認します。