• システム開発に関わる内容をざっくりと書いていく

マイクロサービスの整合:Saga と「イベントをパブリッシュするだけ」

マイクロサービスに分割すると、「複数サービスを跨ぐ更新の整合」がすぐ問題になる。プロセス内なら XA / 2PC も選択肢だが、サービス分割後は事実上使えないことが多く、代わりに Saga パターンが定番になる。

現場では「うちは Saga じゃなくて、イベントバスにパブリッシュするだけ」と言うこともある。実はこの設計、コレオグラフィ型 Saga(イベント駆動)そのものであることが多い。二択ではなく、イベント発行はコレオグラフィ型の実装手段、という整理がわかりやすい。

この記事では、Saga の2タイプTransactional Outbox要件から型を選ぶポイントを短く整理する。


先に結論

  • 跨サービス更新の整合は、基本 Saga(オーケストレーション / コレオグラフィ)
  • 「イベントバスに publish するだけ」= だいたい コレオグラフィ型 Saga
  • 安全な publish の前提は Transactional Outbox+受信側の冪等性
  • 型の選定は好みではなく要件(即時整合・補償の複雑さ・相手障害時に止めてよいか・既存バスの有無)から当てる
  • 実務はハイブリッドが多い。中核はオーケストレーション、波及・通知系はイベント

なぜ 2PC / XA を避けるか(短く)

複数サービスがそれぞれ DB を持つと、分散トランザクションはロックが長くなり可用性も落ちやすい。そこで 結果整合。各サービスはローカル TX だけ正しく完了し、全体は一連のローカル更新+補償で追いかける。これが Saga。


Saga の2タイプ

オーケストレーション型

中央のオーケストレーターが各サービスを順に呼び、失敗時に補償を指示する。フローは追いやすいが、中央が各所を知るのでやや密。複雑な分岐・補償がある中核向き。

コレオグラフィ型

各サービスがイベントを発行し、他が購読して反応する。疎結合で拡張しやすいが、全体像は分散する。「イベントバスにパブリッシュするだけ」はだいたいこちら。


「パブリッシュするだけ」が健全な条件

イベント駆動は妥当だが、publish するだけで安全になるわけではない。肝は次の2つ。

1. Transactional Outbox

業務更新と「イベント発行記録」を同一ローカル TXで書く。別プロセス(リレー / CDC)があとからバスへ配信する。

悪い例: DB 更新コミット → その後 bus.publish
        → 1 成功・2 失敗で「起きた事実」が届かない

良い例: 業務更新 + outbox INSERT を同一 TX
        → リレーが outbox を読んで配信
        → DB に事実があるなら、いつか必ず配信される

典型的な Outbox 行: イベント種別、ペイロード、配信済みフラグ(IsNotified 等)、作成日時・リトライ回数。

2. 受信側の冪等性

少なくとも1回は届く前提。同じイベントが2回来ても壊れないようにする(処理済み ID、状態遷移の冪等化、必要なら順序保証)。

現場の形と注意

  • 業務サービスが outbox に INSERT → リレーが SQS 等へ配信 → 購読側が冪等に更新
  • 波及系は既存 Outbox 基盤への相乗りが第一候補
  • Outbox 前後に S3 等の外部 I/O を挟まない(原子性が緩む)。まず DB に書くだけ

選び方の指針

観点              オーケストレーション型           コレオグラフィ型(イベント発行)
──────────────────────────────────────────────────────────────────────
整合の即時性      比較的取りやすい(同期寄り)     結果整合(遅延前提)
フローの見通し    中央に集約され追いやすい         分散して全体像が見えにくい
結合度            やや密(中央が各所を知る)       疎(発行側は購読側を知らない)
補償              明示的に実装                     補償もイベントで表現
既存バスとの親和  中央が呼び出しを知る必要がある   Outbox + SQS/Kafka と相性が良い
  • 厳密な整合・複雑な補償が要る中核 → オーケストレーション
  • 通知・波及・「事実の共有」 → コレオグラフィ(イベント発行)

「削除直後に必ず未仕訳で見えないと困る」なら、イベントだけに頼らず UI の楽観更新や同期確認を足す。遅延が許容できる波及なら、イベント発行が素直。


要件に合わせた選定のポイント

選定は「どちらが正しいか」ではなく、業務要件を先に言語化してから型を当てる

先に聞く要件(この順番がおすすめ)

  1. 整合のタイミング … 画面に即座に反映必須か、数秒〜数分の遅延でよいか。「削除直後に必ず未仕訳で見えない」ならイベント単体では足りない
  2. 失敗時の扱い(補償) … 途中失敗で巻き戻しが必要か(予約→決済→在庫のような連鎖)。後で追いつければよい波及なら、補償イベントで足りることが多い
  3. 相手障害時に自サービスを止めてよいか … 止めたくないなら非同期(Outbox)必須。同期直呼びは相手ダウンが自業務の失敗になる
  4. 参加者の増え方 … 今は2サービスでも、購読者が増える見込みならコレオグラフィが伸びやすい。参加者が固定で分岐が多いならオーケストレーションが見通し良い
  5. 運用で追いやすさ … 「今どこまで進んだか」をサポートが追う必要が高いか。高いなら中央オーケストレータか、相関ID付きのトレースを最初から設計する
  6. 既存基盤 … Outbox / バスが既にあるなら、新規の同期 IF より相乗りが第一候補

要件 → 型の早見

要件                              寄せる型                         補足
──────────────────────────────────────────────────────────────────────────
即時に全体が揃っている必要がある  オーケストレーション寄り         イベントだけでは遅延が残る
                                  + UI 楽観更新 / 短い同期確認
遅延 OK の波及・通知              コレオグラフィ + Outbox         既存バスがあれば相乗り
補償ステップが3以上・分岐が多い   オーケストレーション             補償を明示する
購読者が今後増える                コレオグラフィ                   発行側は購読側を知らない
相手障害で自業務を止めたくない    非同期(Outbox)                 同期直呼びは避ける
監査・再現性が高い                どちらでも可                     相関ID + Outbox 行が履歴になる
SLO が厳しくフローが単純          同期 API(同一プロセス内)       跨サービスならそれでも補償設計は残る

判定の目安

  • コレオグラフィに寄せる: 遅延許容 / 補償が単純 or 不要 / 購読者が増えうる / Outbox 基盤がある / 相手障害で自業務を止めたくない
  • オーケストレーションに寄せる: ステップ順が業務上重要 / 補償が複雑 / 「今どこ」を一箇所で見たい / 失敗時の人手オペが中央フロー前提
  • イベントだけでは足りない: ユーザーが待つ画面に、他サービス更新済みであることが必須 → 楽観更新か短い確認 API を足す

同一ユースケースでも分ける(ハイブリッド)

実務では、ユーザーが待つ部分裏で追いつく部分を分けることが多い。

例: 仕訳削除 → 明細の未仕訳化

  ユーザー待ち
    自サービスの削除をローカル TX で確定して 200 を返す
    (必要なら画面は楽観的に「未仕訳」表示)

  裏の波及(Outbox)
    同一 TX で JournalDeleted を Outbox へ → リレー配信
    明細側が冪等に未仕訳化

  即時見えが必須のとき
    短い同期確認 API か、画面の再取得を足す

決めてはいけないこと

  • 全部オーケストレーション … 中央が肥大し、通知系まで密結合になる
  • 全部イベント … 即時整合要件を無視し、画面と DB が食い違う
  • Outbox なしの publish … 要件がどちらでも、発行の穴は残る
  • 「既存がイベントだから全部イベント」 … 基盤は活かしつつ、即時が必要な核だけ同期確認を足す

落とし穴とチェックリスト

  • 「Saga かイベントか」は二択ではない。イベント発行はコレオグラフィ型の手段
  • Outbox なしの publish → DB 成功・publish 失敗の穴
  • 受信側の冪等忘れ / Outbox 前後の外部 I/O / 即時整合をイベントだけで期待する
  1. 跨サービス更新か? → はいなら Saga を候補に
  2. 即時整合・補償・相手障害時の停止可否を、業務要件として言語化したか?
  3. イベント発行するなら Outbox(または CDC)はあるか?購読側は冪等か?
  4. 既存の Outbox / イベント基盤に相乗りできないか?
  5. TX 境界に外部 I/O(S3・HTTP)を混ぜていないか?
  6. ユーザー待ちと裏の波及を分けられるか?(全部を同じ型に押し込んでいないか)

まとめ

  • 跨サービス整合は基本 Saga。「publish するだけ」は コレオグラフィ型として一般的
  • 健全さの条件は Transactional Outbox + 受信側冪等
  • 型は要件(タイミング・補償・障害時に止めてよいか・既存基盤)から選ぶ。ユーザー待ちと裏の波及は分けてよい