いうも有益な情報発信にとても感謝しております。僭越ながら質問させて頂きます。
EventSourcing+CQRSに興味がありつつ、クライアントが「コマンドを投げてからクエリとして結果取得できるまで」のタイムラグを許容できないケースが多いのではないかと考えております。
一般的には、あるリソースにPOSTして成功したら、次にGETした時にはその結果を取得できると期待されると思っています。
ES+CQRSでは、クエリモデルを作成するprojectionが非同期に動くため、コマンド成功直後はクエリモデルがまだ生成されていないと認識しています。
この時、クライアント側が認識を変えてコマンド直後にクエリできないかもしれない、という前提で実装することも考えられますが、Spring WebFlux を使うと解決できるものなのでしょうか?
クライアントがクエリモデルのストリームを購読していれば、projectionの結果をリアクティブに受け取れると思いました。
ES+CQRSの場合に、WebFluxを採用することについてのお考えをお聞かせ頂ければ幸いです。
よろしくお願いします。

かとじゅんさん
「POST直後に即GETで最新データを返すのは本当に必要ですか?」
CQRSやEvent Sourcingを導入すると「コマンドを投げてから、クエリ結果に反映されるまでタイムラグがある」問題が目立ちがちですが、これはES+CQRS特有のものではありません。リプリケーション構成(ライター/リーダー)を取るRDBでも、非同期でリーダーにデータが反映される以上「書き込み直後に読みたい場合はライターに問い合わせる」ことが必要になります。秒単位の遅延も珍しくなく、書き込み→読み取りが完全に同期するわけではありません。
> 一般的には、あるリソースにPOSTして成功したら、次にGETした時にはその結果を取得できると期待されると思っています。
この「必ず即時反映」が現実的に必要かどうか、あらためて考えてみてください。
たとえば TwitterやFacebookのAPIも、投稿直後にリストを見に行くと投稿がまだ見えないことがありますよね。大規模分散環境では「結果整合性」の考え方が普通に受け入れられており、ユーザも「若干のラグ」を自然に許容しています。
「書き込んですぐ読める」という仕組みは確かに使い勝手が良さそうですが、その裏では「すべてを同期的に処理しないといけない」ため、書き込み性能が落ちたりスケールしにくくなるというトレードオフが存在します。クラスタ構成のRDBでも「即時整合が絶対必要な箇所」だけはライターに問い合わせ、それ以外はリーダーへ流すなどの工夫をしますが、常にそれをやるとライター側に負荷が集中してしまいます。これはES+CQRSでもまったく同じで、「すぐに読める=すぐに書き込めない」のが非同期アーキテクチャの宿命です。
どうしても「必ず即時反映」が必要な領域があるなら、
- UI/UXでラグを吸収して「更新に少し時間がかかる」ことを表示しておく
- 部分的に同期的更新を導入して書き込み完了=Projection完了を待つ
- 書き込みモデルを直接再構築して応答を返す(Event Sourcingの場合は集約を再構築)
といった選択肢があります。ただしどれも、一貫して「スケーラビリティと即時整合をどうトレードオフするか」という問題です。
実際、Twitterレベルの大規模サービスですら「投稿が即反映されない」ことは日常茶飯事です。大量の書き込みに耐えるために、あえて結果整合性をとっているからです。加えて、どうしても「自分だけはすぐ見たい」投稿は、エコーバック(フロントエンドで先に表示する)でユーザ体験を良くするなど、技術的というよりUXの工夫でカバーする方法もあります。
要するに、ES+CQRSだろうがRDBだろうが「書き込みと読み取りを分離して非同期で反映する構成」では同様のタイムラグ問題が起こり得るわけです。即時反映にこだわると書き込みパフォーマンスやスケールの問題が避けられないので、どのサービスもこれをどう吸収するか設計しているというのが実情です。
---
ポーリングを減らす工夫について
非同期でProjectionが更新されるシステムの場合、クライアントが最新データを取得しようとするとポーリングになりがちです。しかしこのやり方はサーバーに不要なリクエストを多発させる可能性があります。
クライアントがコマンドを投げた後、リードモデル(またはProjection)の更新をサーバーがプッシュ通知する形をとれば、クライアントは「必要なデータが更新されたタイミング」だけを受け取り、無駄なポーリングを減らせます。
Spring WebFluxでもWebSocketやSSEを利用して、イベントストリームをクライアントにリアクティブに送れます。(非同期更新とはいえ、ほとんどのケースで書き込んですぐに読めるはずなので、この仕組みが本当に必要か慎重に検討してください)ただし、結果整合性の問題自体を解決するものではなく、あくまで「いつ更新されたかを効率的に察知してUIを更新する」方法に過ぎません。
つまり、「書き込み→読み取りの非同期構造(最終的整合性)」は変わらないけれど、プッシュベースで更新通知を受け取ることで、クライアントが無闇にポーリングしなくても最新情報をキャッチできるようになります。これによってサーバー負荷も抑えられ、UXも改善できると思います。