【ITニュース解説】How I Learned to Stop Worrying and Love Raw Events, Event Sourcing & CQRS with FastAPI and Celery
2025年09月03日に「Dev.to」が公開したITニュース「How I Learned to Stop Worrying and Love Raw Events, Event Sourcing & CQRS with FastAPI and Celery」について初心者にもわかりやすいように丁寧に解説しています。
ITニュース概要
システムが過去の操作履歴を失い、問題解決が困難になる課題を、イベントソーシングとCQRSで解決する。全ての変更を「イベント」として記録し、システムの完全な履歴を保持することで、デバッグや障害原因の特定を容易にする。PythonのFastAPIとCeleryを使い、効率的な実装と高いスケーラビリティを実現できる。
ITニュース解説
多くのシステム開発者が経験する共通の悩みは、システム上で「何が起こったのか」を正確に把握できないことである。たとえば、あるユーザーのアカウントが突然消滅した場合、従来のシステムでは「いつ」「誰が」「なぜ」削除されたのかを特定するのが困難になる場合が多い。これは、従来のデータベース設計が「現在の状態」のみを保存し、変更履歴を十分に保持しないことに起因する。データが更新または削除されると、以前の情報は上書きされたり完全に失われたりするため、過去の経緯を追跡する能力が著しく低いという根本的な課題がある。最善の対策としてソフトデリート(論理削除)を導入しても、削除の理由や、それに至るまでの詳細な経緯といった文脈は失われがちである。
このような問題に対する根本的な解決策として、「イベントソーシング」というアプローチがある。イベントソーシングでは、システム内で発生するすべての変更を「イベント」という不変の事実として永続的に記録する。イベントは、一度発生したら変更や削除ができない確かな情報であり、システムの「DNA」とも言える。例えば、ユーザーが作成された、ユーザー名が変更された、ユーザーが削除された、といったあらゆる操作がそれぞれのイベントとして記録される。これらのイベントは「イベントストア」と呼ばれる追記専用のストレージに保存され、各ユーザーやエンティティの「ストーリー」として時系列に並べられる。この完全なイベントの履歴があるため、いつでも過去の任意の時点の状態をイベントを再生することで正確に再現できる。「タイムトラベル」のようなデバッグ能力や監査能力がこれによって得られる。
イベントソーシングを効果的に活用するために、「CQRS(Command Query Responsibility Segregation)」という設計パターンが組み合わされることが多い。これは、システムの状態を変更する「コマンド」(書き込み操作)と、システムの状態を問い合わせる「クエリ」(読み込み操作)の責任を完全に分離するという考え方である。コマンドは「新しいユーザーを作成したい」や「パスワードを変更したい」といったシステムへの意図を表現し、ビジネスルールに従って検証された後にイベントを生成する。一方、クエリはデータの読み取りに特化し、特定の用途に最適化された「リードモデル」から直接データを取得する。この分離により、書き込み処理はデータの整合性と一貫性を保つことに集中でき、読み込み処理は高いパフォーマンスと多様なアクセスパターンに対応できる柔軟性を得られる。
ビジネスロジックは「アグリゲート」と呼ばれる単位にカプセル化される。アグリゲートはコマンドを受け取ると、イベントストアから過去のイベントを読み込み、現在の状態を再構築する。そして、コマンドに対するビジネスルールを適用し、検証が成功すれば新たなイベントを生成する。生成されたイベントはイベントストアに保存され、同時に「イベントバス」を通じて他のコンポーネントに通知される。このイベントを受け取り、読み取り専用のリードモデルを構築するのが「プロジェクション」である。プロジェクションはイベントを購読し、それを元にデータベースのレコードを更新したり、検索インデックスを構築したり、あるいは歓迎メールを送信したりといった様々な副次的な処理を実行する。これにより、一つのイベントから複数の異なるアクションが引き起こされ、システムの各部分が疎結合に連携できるようになる。
このようなイベントソーシングとCQRSのアーキテクチャは、Pythonのエコシステムで堅牢に構築できる。WebインターフェースにはFastAPIを使用し、ユーザーからのコマンドやクエリを受け付けるAPIエンドポイントを構築する。FastAPIの依存性注入の仕組みを利用して、適切なコマンドハンドラやクエリハンドラを簡単に割り当てることが可能だ。コマンドハンドラは、イベントストアからアグリゲートの履歴イベントを取得し、アグリゲートの状態を再構築してビジネスロジックを実行する。そこで生成された新しいイベントはイベントストアに保存され、Celeryなどの非同期タスクキューを介して「イベントハンドラ」にディスパッチされる。Celeryタスクはイベントを受け取ると、対応するプロジェクションハンドラを呼び出し、リードモデルを更新したり、その他のサイドエフェクトを実行したりする。これにより、書き込みと読み込みの処理パスが明確に分離され、非同期でイベントドリブンなシステムが実現される。
実運用においては、いくつか考慮すべき課題も存在する。一つは「結果整合性」と呼ばれるもので、コマンドが成功してイベントが保存された後も、読み取り側のリードモデルにその変更が即座に反映されない場合がある点である。ユーザーが情報を更新しても、画面をリロードすると古い情報が表示されるといったUX上の問題につながる可能性がある。これに対しては、フロントエンドで一時的に更新結果を表示する「楽観的更新」や、アウトボックスパターンを用いてイベント処理のステータスを追跡するといった対策が考えられる。もう一つの課題は、イベントストリームが長くなることによるパフォーマンスの低下である。長期間にわたって活動しているユーザーのアグリゲートを再構築する際、数千、数万のイベントを毎回再生するのは非効率である。この問題は、「スナップショット」を導入することで解決できる。定期的にアグリゲートの現在の状態をスナップショットとして保存しておき、アグリゲートを再構築する際には最新のスナップショットから開始し、それ以降の少数のイベントだけを再生することで、パフォーマンスを大幅に改善できる。
しかし、これらの課題を考慮しても、イベントソーシングが提供するメリットは非常に大きい。何よりも、システムが完全な履歴を保持するため、過去の任意の時点のシステム状態を再現し、問題が発生した際に「何が起こったのか」を正確に把握できる「デバッグ能力」は圧倒的である。これは、テスト環境で問題を再現しようとする従来のデバッグ方法に比べ、実際の生産環境で発生した問題を正確に分析できるという点で非常に強力だ。また、システムの状態がイベントの集まりで表現されるため、監査証跡が自動的に作成され、コンプライアンス要件への対応も容易になる。書き込みと読み込みの分離は、それぞれのコンポーネントを独立してスケーリングできるため、システムの柔軟性と拡張性を高める。
イベントソーシングはすべてのシステムに適しているわけではない。単純なCRUD操作が主体で、詳細な監査証跡が不要なシステムであれば、従来のアーキテクチャの方が簡潔で実装コストも低い。また、ミリ秒単位での厳密なリアルタイム一貫性が求められる取引システムなどでは、イベントソーシングの結果整合性モデルは課題となる可能性がある。さらに、イベントソーシングや分散システムの概念に不慣れなチームにとっては、学習コストや導入の複雑さが増すことも考慮すべき点である。しかし、複雑なビジネスロジックを持ち、変更履歴の追跡、高い監査性、将来的な分析や機能拡張の柔軟性が重要なシステムにおいては、イベントソーシングとCQRSは強力な選択肢となる。PythonのFastAPIやCeleryといった成熟したツール群は、このような複雑なアーキテクチャを堅牢かつ効率的に実装するための十分な基盤を提供しており、システムに「完全な記憶」と「自己説明能力」をもたらすことができる。