ひとことで言うと#
「何かが起きた」というイベントをシステム間でやり取りすることで、サービス同士を直接呼び合わない疎結合な設計を実現するアーキテクチャ。「注文が作成された」というイベントを発行すれば、在庫・決済・通知が各自で反応する。
押さえておきたい用語#
- プロデューサー(Producer)
- イベントを発行する側のサービスのこと。イベントを発行するだけで、誰が受け取るかは知らない。この「無関心さ」が疎結合の鍵。
- コンシューマー(Consumer)
- イベントを受け取る側のサービスのこと。関心のあるイベントだけを購読し、自律的に処理する。1つのイベントに複数のコンシューマーが存在できる。
- メッセージブローカー(Message Broker)
- イベントの仲介役となるミドルウェアのこと。Apache Kafka、RabbitMQ、AWS SNS/SQSなどが代表例。イベントの永続化・順序保証・配信保証を担う。
- 冪等性(Idempotency)
- 同じイベントを2回処理しても結果が変わらない性質のこと。ネットワーク障害やリトライで同一イベントが重複配信される場合に不可欠な設計要件。
- サガパターン(Saga Pattern)
- 複数サービスにまたがるトランザクションを、イベントの連鎖で管理するパターンのこと。分散トランザクションの代替として使われる。
イベント駆動アーキテクチャの全体像#
こんな悩みに効く#
- サービスAがサービスB・C・Dを順番に呼ぶ処理チェーンが複雑で壊れやすい
- 新しいサービスを追加するたびに、既存サービスのコードを変更する必要がある
- 同期処理のレスポンスが遅く、ユーザー体験が悪い
基本の使い方#
システムで起きる重要な出来事をイベントとして定義する。
- イベントは過去形で命名する(例:OrderCreated、PaymentCompleted)
- イベントには「何が・いつ・どのように変わったか」の情報を含める
- ビジネスの言葉でイベントを定義する
ポイント: イベントは「起きた事実」を伝えるだけ。「何をしてほしい」は含めない。
イベントを発行する側と、受け取る側を決める。
- プロデューサーはイベントを発行するだけ。誰が受け取るかは知らない
- コンシューマーは関心のあるイベントだけを購読する
- 1つのイベントに対して複数のコンシューマーが存在できる
ポイント: プロデューサーとコンシューマーがお互いを知らないことが疎結合の鍵。
イベントの仲介役となるブローカーを導入する。
- Apache Kafka: 大量のイベントストリーミング、順序保証
- RabbitMQ: 柔軟なルーティング、ワーカーキュー
- AWS SNS/SQS、Google Pub/Sub: マネージドサービス
ポイント: 要件に合わせて選定する。「とりあえずKafka」は過剰になりがち。
イベント処理の失敗や順序の問題に対処する。
- デッドレターキュー: 処理に失敗したイベントを退避させる
- 冪等性: 同じイベントを2回処理しても結果が変わらないようにする
- サガパターン: 複数サービスにまたがるトランザクションを管理する
ポイント: 非同期処理の最大の課題はエラーハンドリング。ここを手抜きすると本番で痛い目に遭う。
具体例#
Before(同期): 注文API → 在庫確認API呼び出し → 決済API呼び出し → 通知API呼び出し → レスポンス返却。全部で3秒。1つでも失敗すると全体がエラー。
After(イベント駆動):
- 注文サービスが注文を受け付け、
OrderCreatedイベントを発行。即座にレスポンス返却(200ms) - 在庫サービスが
OrderCreatedを受信し、在庫を確保。InventoryReservedを発行 - 決済サービスが
InventoryReservedを受信し、決済を実行。PaymentCompletedを発行 - 通知サービスが
PaymentCompletedを受信し、確認メールを送信
結果: レスポンスタイムが3秒→200ms(93%短縮)。通知サービスが落ちていても注文・決済は正常に処理される。
状況: ECサイトに「ポイント付与サービス」を新規追加したい。
同期アーキテクチャの場合: 決済サービスのコードを変更して「決済完了後にポイントサービスを呼び出す」処理を追加。決済サービスのテスト・デプロイが必要。
イベント駆動の場合: ポイント付与サービスがPaymentCompletedイベントを購読するだけ。既存のサービスのコード変更はゼロ。決済サービスは新サービスの存在を知らない。
結果: 新サービスの追加にかかった期間は3日。既存サービスへのリグレッションリスクがゼロ。
問題: ネットワーク障害でKafkaからのイベント再送が発生し、同一のPaymentCompletedイベントが2回配信された。決済サービスがポイントを二重付与してしまい、500ユーザーに過剰ポイントが発行された。
冪等性の導入:
- 各イベントに一意の
eventIdを付与 - コンシューマー側で処理済み
eventIdをRedisに記録(TTL: 7日) - イベント受信時に
eventIdの重複チェックを実行。処理済みならスキップ
結果: 同一イベントの重複処理が構造的にゼロに。Kafkaの再配信やリトライが発生しても安全に動作。月間200万件のイベント処理で重複事故ゼロを維持。
やりがちな失敗パターン#
- すべてを非同期にする — ユーザーが即座に結果を知りたい操作(ログイン、残高照会など)まで非同期にすると体験が悪化する。同期と非同期を適切に使い分ける
- イベントの設計が雑 — イベントに必要な情報が足りず、コンシューマーが結局APIを呼び戻す。イベントに必要十分な情報を含めること
- デバッグが困難になる — イベントの流れが追えず、障害時に原因を特定できない。分散トレーシング(Jaeger、Zipkin等)を必ず導入する
- 冪等性を考慮しない — ブローカーのリトライやat-least-once配信で同一イベントが複数回届く。すべてのコンシューマーに冪等性を実装する
まとめ#
イベント駆動アーキテクチャは、サービス間の結合度を下げ、拡張性を高める強力なパターン。ただし、結果整合性やエラーハンドリングの複雑さというトレードオフがある。まずは 「通知」 や「ログ」など、結果整合性が許される部分から導入して、徐々に適用範囲を広げよう。