イベント駆動型アーキテクチャ入門
はじめに
これはイベント駆動型アーキテクチャの概要を説明するためのドキュメントです。
- なぜ、イベント駆動型アーキテクチャが必要なのか
- イベント駆動型アーキテクチャとはどのようなものか
- どのようなトレードオフがあるのか
について触れます。
逆に、個別具体的なユースケースについては触れません。
あくまで、イベント駆動型アーキテクチャを採用する上で知っておくべきこと、考えるべきことを記述します。
アーキテクチャ設計をしなければならない、となったときに考える軸の一助となれば幸いです。
イベント駆動型アーキテクチャ以前
モノリスとマイクロサービス
単一のサーバーに様々な機能を詰め込むアーキテクチャには、以下のような課題があります。
- 機能ごとのスケールができない
- ある機能の障害が別の機能まで伝播する
- デプロイの影響範囲が大きい
これを解決するための手法としてマイクロサービスアーキテクチャがあります。
マイクロサービスとは、モノリスなアプリケーションを機能ごとに分割し、それぞれ独立したサービスとして設計・開発・運用する手法です。
マイクロサービスアーキテクチャの課題と分散モノリス
マイクロサービスアーキテクチャでは、サービス間を同期通信(REST APIやgRPCなど)でつなぐのが一般的です。
しかし、この方式には以下の課題があります。
- 可用性の連鎖: サービスAがBを呼び、BがCを呼ぶ構成の場合、Cが停止するとBも失敗し、最終的にAも失敗します
- スケーリングの連鎖: Aをスケールアップさせた場合、BおよびCも同様にスケールアップさせなければいけないかもしれません
- インターフェイスの結合: AはBのインターフェイスを知っている必要があるため、Bの修正がAにも影響し、独立したデプロイができなくなります
このようにサービスを分離したにも関わらず、モノリスが抱えていた問題が生じることを「分散モノリス」と呼びます。
こういったサービス間の結合の問題を解決する一つの手法として、イベント駆動型アーキテクチャがあります。
イベント駆動型アーキテクチャとは何か
イベント駆動型アーキテクチャ (EDA) は、サービス間の直接的な呼び出しを避け、「何かが起きた」という事実(イベント)を通知し合うことで、結合を断ち切ります。
これにより、サービス全体の耐障害性を高め、必要な部分だけをスケールさせやすくし、各サブシステムを独立に開発できるようにします。
EDAは、古典的な同期連携の課題を解決するために生まれた設計です。
主要な構成要素
EDAは主に以下の3つの要素で構成されます。
プロデューサ (Producer)
「イベント」を生成し、ブローカへ送信する役割です。
ブローカ (Broker)
プロデューサとコンシューマの間でイベントを中継する役割です。ブローカは以下のような機能を組み合わせて提供します。
主な機能
- ルーティング : イベントの内容や属性に基づいて、適切な宛先へ振り分ける
- フィルタリング : 条件に合致しないイベントを破棄し、必要なイベントだけを通過させる
- コピー : 1つのイベントを複数の宛先にコピーして送る
- ストア : イベントを一時的または永続的に保存しる。コンシューマがダウンしている間もイベントを保持できる
- アグリゲート : 複数のイベントを1つにまとめる
コンシューマ (Consumer)
ブローカからイベントを受け取り、実際のビジネスロジックを実行する役割です。
「イベント」とは何か
イベントは「過去に起きた事実」を表す記録です。状態変化が起きたことを事実として通知し、後続処理のきっかけにします。
イベントが満たすべき条件
- 過去の事実として表現される(命令形ではなく、過去分詞形で命名することが多い)
- 事実を示す十分な情報を含む(誰が/何が/いつ、など)
- 変更不可(immutable)として扱う
- 発行者は受け手を知らない(プロデューサとコンシューマは分離される)
注意点(取消はできない)
過去の事実は取り消せないため、イベントを「削除」してなかったことにはできません。訂正や取消が必要な場合は、別の事実として新しいイベントを追加します。
補足: Commandとの違い
Commandは「〜せよ」という命令であり、送信者は宛先を特定して依頼します。イベントは「〜が起きた」という事実の通知であり、送信者は受信者の存在を意識しません。この違いによって、EDAではプロデューサとコンシューマの結合を避けられます。
利点と注意点
利点
- 独立した進化 : 既存のプロデューサに変更を加えることなく、新しいコンシューマを後付けで追加できます。
- 耐障害性 : 受信側のサービスがダウンしていても、ブローカがイベントを保持するため、復旧後に処理を再開できます
- スケーラビリティ: 負荷の高い特定の処理だけを、イベントの量に応じてオンデマンドでスケールさせることが容易です
注意点(設計上のトレードオフ)
通信が非同期になるため、開発者は「リクエストに対する即時のレスポンス」という前提を捨てる必要があります。
- 結果整合性 : 全てのデータが瞬時に更新されるわけではなく、「数秒後には正しくなっている」という状態を許容する必要があります
- 冪等性の担保: ネットワーク障害による再送などで、同じイベントが2回届く可能性があります。2回処理しても結果が変わらない設計が必須です
- デバッグと観測の難しさ: 処理のフローがコードを一目見ただけでは追えないため、分散トレーシング(OpenTelemetry等)の導入が必要となります
結果整合性担保の仕組み
分散環境における結果整合性担保の仕組みとして、古典的には2PCが用いられてきました。
ただし2PCはコーディネータが単一障害点になるなどの課題があります。
そのため、Sagaパターンが採用されることがあります。
Sagaパターンは補償トランザクションによってロールバックを実現し、2PCの課題を回避します。
一方でACID特性はサポートされずACDのみとなるため、更新消失、ダーティリード、反復不能読み取りが起こり得ます。
また、実装も複雑です。
イベント駆動型アーキテクチャのパターン
ブローカの種類によって、主に3つの型に分類されます。それぞれ得意な場面が異なるため、要件に応じて使い分けます。
ポイントツーポイント (Queue)
- 仕組み: 1つのイベントを、必ず1つのコンシューマが処理する
- 用途: 重たい非同期処理の分散、複数ワーカーによる負荷分散、処理順序の保証が必要な場面
- 選定基準: 「1つのイベントを1回だけ確実に処理したい」場合に向いています。一方で、同じイベントを複数のサービスで受け取りたい場合には不向きです
- 代表的なAWSサービス: Amazon SQS、Amazon MQ
パブリッシュ/サブスクライブ (Pub/Sub)
- 仕組み: 1つのイベントを、それを購読している複数のコンシューマへコピーして届ける
- 用途: 1つのアクションに対して複数の後続処理を並行して走らせる場面、システム間の疎結合な連携、リアルタイム通知のブロードキャスト
- 選定基準: 「1つのイベントを複数のサービスがそれぞれ受け取りたい」場合に向いています。一方で、イベントは基本的に届けたら消えるため、後から過去のイベントを再生したい場合には不向きです
- 代表的なAWSサービス: Amazon SNS、Amazon EventBridge
イベントストリーミング
- 仕組み: イベントを「追記専用のログ」として永続化します。新しいイベントは末尾に追加され、コンシューマは自分の読み取り位置を管理しながらログを順番に読み進めます。Pub/Subと異なり、イベントは配信後も一定期間保持されます
- 用途: 過去のイベントを遡って再処理する場面、リアルタイムのデータパイプライン、イベントソーシング、監査ログ
- 選定基準: 「イベントの履歴を保持し、後から再生・分析したい」場合に向いています
- 代表的なAWSサービス: Amazon Kinesis Data Streams、Amazon MSK
まとめ
- イベント駆動型アーキテクチャは、分散モノリスの課題を解決するために、プロデューサとコンシューマの間にブローカを挟んで結合を断ち切るアーキテクチャ
- ブローカの種類(Queue、Pub/Sub、ストリーミング)によって特性が異なるため、要件に応じて選択する
- 結果整合性やべき等性など、分散アーキテクチャ特有の設計課題があり、銀の弾丸ではなく、その他のアーキテクチャ同様、トレードオフを意識して選択することが重要




