イベント駆動アーキテクチャ

英語名 Event-Driven Architecture
読み方 イベント ドリブン アーキテクチャ
難易度
所要時間 設計に1〜2週間
提唱者 分散システム設計から自然発生的に確立
目次

ひとことで言うと
#

「何かが起きた」というイベントをシステム間でやり取りすることで、サービス同士を直接呼び合わない疎結合な設計を実現するアーキテクチャ。「注文が作成された」というイベントを発行すれば、在庫・決済・通知が各自で反応する。

押さえておきたい用語
#

押さえておきたい用語
プロデューサー(Producer)
イベントを発行する側のサービスのこと。イベントを発行するだけで、誰が受け取るかは知らない。この「無関心さ」が疎結合の鍵。
コンシューマー(Consumer)
イベントを受け取る側のサービスのこと。関心のあるイベントだけを購読し、自律的に処理する。1つのイベントに複数のコンシューマーが存在できる。
メッセージブローカー(Message Broker)
イベントの仲介役となるミドルウェアのこと。Apache Kafka、RabbitMQ、AWS SNS/SQSなどが代表例。イベントの永続化・順序保証・配信保証を担う。
冪等性(Idempotency)
同じイベントを2回処理しても結果が変わらない性質のこと。ネットワーク障害やリトライで同一イベントが重複配信される場合に不可欠な設計要件。
サガパターン(Saga Pattern)
複数サービスにまたがるトランザクションを、イベントの連鎖で管理するパターンのこと。分散トランザクションの代替として使われる。

イベント駆動アーキテクチャの全体像
#

イベント駆動アーキテクチャ:プロデューサー/ブローカー/コンシューマーの構造
Producer注文サービス「OrderCreated」イベントを発行誰が受け取るかは知らない(疎結合)Message BrokerKafka / RabbitMQイベントの仲介・永続化順序保証・配信保証デッドレターキューで失敗イベントを退避Consumers在庫サービス決済サービス通知サービス各自が独立してイベントに反応新サービス追加も容易エラーハンドリングの設計が最重要冪等性: 2回処理しても同じ結果DLQ: 失敗イベントの退避サガパターン: 分散トランザクション管理非同期処理の最大の課題はエラーハンドリング
イベント駆動アーキテクチャ導入フロー
1
イベント定義
ビジネスの出来事を過去形で命名
2
P/C設計
プロデューサーとコンシューマーを決定
3
ブローカー導入
要件に合ったメッセージブローカーを選定
エラー設計
冪等性・DLQ・サガパターンを実装

こんな悩みに効く
#

  • サービスAがサービスB・C・Dを順番に呼ぶ処理チェーンが複雑で壊れやすい
  • 新しいサービスを追加するたびに、既存サービスのコードを変更する必要がある
  • 同期処理のレスポンスが遅く、ユーザー体験が悪い

基本の使い方
#

ステップ1: イベントを定義する

システムで起きる重要な出来事をイベントとして定義する

  • イベントは過去形で命名する(例:OrderCreated、PaymentCompleted)
  • イベントには「何が・いつ・どのように変わったか」の情報を含める
  • ビジネスの言葉でイベントを定義する

ポイント: イベントは「起きた事実」を伝えるだけ。「何をしてほしい」は含めない。

ステップ2: プロデューサーとコンシューマーを設計する

イベントを発行する側と、受け取る側を決める

  • プロデューサーはイベントを発行するだけ。誰が受け取るかは知らない
  • コンシューマーは関心のあるイベントだけを購読する
  • 1つのイベントに対して複数のコンシューマーが存在できる

ポイント: プロデューサーとコンシューマーがお互いを知らないことが疎結合の鍵。

ステップ3: メッセージブローカーを選定・導入する

イベントの仲介役となるブローカーを導入する

  • Apache Kafka: 大量のイベントストリーミング、順序保証
  • RabbitMQ: 柔軟なルーティング、ワーカーキュー
  • AWS SNS/SQS、Google Pub/Sub: マネージドサービス

ポイント: 要件に合わせて選定する。「とりあえずKafka」は過剰になりがち。

ステップ4: エラーハンドリングと整合性を設計する

イベント処理の失敗や順序の問題に対処する

  • デッドレターキュー: 処理に失敗したイベントを退避させる
  • 冪等性: 同じイベントを2回処理しても結果が変わらないようにする
  • サガパターン: 複数サービスにまたがるトランザクションを管理する

ポイント: 非同期処理の最大の課題はエラーハンドリング。ここを手抜きすると本番で痛い目に遭う。

具体例
#

例1:ECサイトの注文処理を同期から非同期に変えてレスポンスを93%短縮する

Before(同期): 注文API → 在庫確認API呼び出し → 決済API呼び出し → 通知API呼び出し → レスポンス返却。全部で3秒。1つでも失敗すると全体がエラー。

After(イベント駆動):

  1. 注文サービスが注文を受け付け、OrderCreatedイベントを発行。即座にレスポンス返却(200ms
  2. 在庫サービスがOrderCreatedを受信し、在庫を確保。InventoryReservedを発行
  3. 決済サービスがInventoryReservedを受信し、決済を実行。PaymentCompletedを発行
  4. 通知サービスがPaymentCompletedを受信し、確認メールを送信

結果: レスポンスタイムが3秒→200ms93%短縮)。通知サービスが落ちていても注文・決済は正常に処理される。

例2:新サービス追加を既存コード変更ゼロで実現する

状況: ECサイトに「ポイント付与サービス」を新規追加したい。

同期アーキテクチャの場合: 決済サービスのコードを変更して「決済完了後にポイントサービスを呼び出す」処理を追加。決済サービスのテスト・デプロイが必要。

イベント駆動の場合: ポイント付与サービスがPaymentCompletedイベントを購読するだけ。既存のサービスのコード変更はゼロ。決済サービスは新サービスの存在を知らない。

結果: 新サービスの追加にかかった期間は3日。既存サービスへのリグレッションリスクがゼロ

例3:冪等性の設計で重複処理を防ぐ

問題: ネットワーク障害でKafkaからのイベント再送が発生し、同一のPaymentCompletedイベントが2回配信された。決済サービスがポイントを二重付与してしまい、500ユーザーに過剰ポイントが発行された。

冪等性の導入:

  • 各イベントに一意のeventIdを付与
  • コンシューマー側で処理済みeventIdをRedisに記録(TTL: 7日)
  • イベント受信時にeventIdの重複チェックを実行。処理済みならスキップ

結果: 同一イベントの重複処理が構造的にゼロに。Kafkaの再配信やリトライが発生しても安全に動作。月間200万件のイベント処理で重複事故ゼロを維持。

やりがちな失敗パターン
#

  1. すべてを非同期にする — ユーザーが即座に結果を知りたい操作(ログイン、残高照会など)まで非同期にすると体験が悪化する。同期と非同期を適切に使い分ける
  2. イベントの設計が雑 — イベントに必要な情報が足りず、コンシューマーが結局APIを呼び戻す。イベントに必要十分な情報を含めること
  3. デバッグが困難になる — イベントの流れが追えず、障害時に原因を特定できない。分散トレーシング(Jaeger、Zipkin等)を必ず導入する
  4. 冪等性を考慮しない — ブローカーのリトライやat-least-once配信で同一イベントが複数回届く。すべてのコンシューマーに冪等性を実装する

まとめ
#

イベント駆動アーキテクチャは、サービス間の結合度を下げ、拡張性を高める強力なパターン。ただし、結果整合性やエラーハンドリングの複雑さというトレードオフがある。まずは 「通知」 や「ログ」など、結果整合性が許される部分から導入して、徐々に適用範囲を広げよう。