マイクロサービスに分割すると、「複数サービスを跨ぐ更新の整合」がすぐ問題になる。プロセス内なら XA / 2PC も選択肢だが、サービス分割後は事実上使えないことが多く、代わりに Saga パターンが定番になる。
現場では「うちは Saga じゃなくて、イベントバスにパブリッシュするだけ」と言うこともある。実はこの設計、コレオグラフィ型 Saga(イベント駆動)そのものであることが多い。二択ではなく、イベント発行はコレオグラフィ型の実装手段、という整理がわかりやすい。
この記事では、Saga の2タイプ、Transactional Outbox、要件から型を選ぶポイントを短く整理する。
複数サービスがそれぞれ DB を持つと、分散トランザクションはロックが長くなり可用性も落ちやすい。そこで 結果整合。各サービスはローカル TX だけ正しく完了し、全体は一連のローカル更新+補償で追いかける。これが Saga。
中央のオーケストレーターが各サービスを順に呼び、失敗時に補償を指示する。フローは追いやすいが、中央が各所を知るのでやや密。複雑な分岐・補償がある中核向き。
各サービスがイベントを発行し、他が購読して反応する。疎結合で拡張しやすいが、全体像は分散する。「イベントバスにパブリッシュするだけ」はだいたいこちら。
イベント駆動は妥当だが、publish するだけで安全になるわけではない。肝は次の2つ。
業務更新と「イベント発行記録」を同一ローカル TXで書く。別プロセス(リレー / CDC)があとからバスへ配信する。
悪い例: DB 更新コミット → その後 bus.publish
→ 1 成功・2 失敗で「起きた事実」が届かない
良い例: 業務更新 + outbox INSERT を同一 TX
→ リレーが outbox を読んで配信
→ DB に事実があるなら、いつか必ず配信される
典型的な Outbox 行: イベント種別、ペイロード、配信済みフラグ(IsNotified 等)、作成日時・リトライ回数。
少なくとも1回は届く前提。同じイベントが2回来ても壊れないようにする(処理済み ID、状態遷移の冪等化、必要なら順序保証)。
観点 オーケストレーション型 コレオグラフィ型(イベント発行)
──────────────────────────────────────────────────────────────────────
整合の即時性 比較的取りやすい(同期寄り) 結果整合(遅延前提)
フローの見通し 中央に集約され追いやすい 分散して全体像が見えにくい
結合度 やや密(中央が各所を知る) 疎(発行側は購読側を知らない)
補償 明示的に実装 補償もイベントで表現
既存バスとの親和 中央が呼び出しを知る必要がある Outbox + SQS/Kafka と相性が良い
「削除直後に必ず未仕訳で見えないと困る」なら、イベントだけに頼らず UI の楽観更新や同期確認を足す。遅延が許容できる波及なら、イベント発行が素直。
選定は「どちらが正しいか」ではなく、業務要件を先に言語化してから型を当てる。
要件 寄せる型 補足
──────────────────────────────────────────────────────────────────────────
即時に全体が揃っている必要がある オーケストレーション寄り イベントだけでは遅延が残る
+ UI 楽観更新 / 短い同期確認
遅延 OK の波及・通知 コレオグラフィ + Outbox 既存バスがあれば相乗り
補償ステップが3以上・分岐が多い オーケストレーション 補償を明示する
購読者が今後増える コレオグラフィ 発行側は購読側を知らない
相手障害で自業務を止めたくない 非同期(Outbox) 同期直呼びは避ける
監査・再現性が高い どちらでも可 相関ID + Outbox 行が履歴になる
SLO が厳しくフローが単純 同期 API(同一プロセス内) 跨サービスならそれでも補償設計は残る
実務では、ユーザーが待つ部分と裏で追いつく部分を分けることが多い。
例: 仕訳削除 → 明細の未仕訳化
ユーザー待ち
自サービスの削除をローカル TX で確定して 200 を返す
(必要なら画面は楽観的に「未仕訳」表示)
裏の波及(Outbox)
同一 TX で JournalDeleted を Outbox へ → リレー配信
明細側が冪等に未仕訳化
即時見えが必須のとき
短い同期確認 API か、画面の再取得を足す