
Saga(サーガ)パターンは「分散システム」や「マイクロサービス」で重要なデザインパターンです。
そして、分散トランザクションを管理するためのパターンでもあります。
「1つの大きな処理(例:注文)」が複数のサービスにまたがるとき、
各サービスが独立して局所的にコミットし、失敗時には補償(キャンセル)処理で整合性を保つのが特徴です。
このパターンの考え方
「ECサイトでの注文フロー」を例に考えてみます。
たとえば「注文処理」を1つのサービスで完結できない場合に、
注文サービス → 注文を登録
決済サービス → 支払いを実行
在庫サービス → 在庫を減らす
もし、途中で決済が失敗した場合、全体をロールバックできない(分散してるから)事態が発生します。
ではどうするかというと、補償トランザクションを実行して整合性をとるという流れになります。
Sagaパターンは、こういう思考で設計するんですね。
Sagaの位置付け
| 観点 |
内容 |
| トランザクション管理 |
分散システム向けのロールバック制御 |
| 主な使用領域 |
マイクロサービス、イベント駆動アーキテクチャ |
| 関連パターン |
| Event Sourcing, CQRS, Outbox Pattern |
| 実装例 |
AWS Step Functions, Temporal.io, Axon Framework |
Sagaの2つの制御モデル
Sagaパターンには、次の2つの制御モデルがあります。
1. Choreography(振付型)
2. Orchestration(オーケストレーション型)
それぞれの説明は以下の通り。
1. Choreography(振付型)
各サービスがイベントを購読/発行して連携するモデルです。
中央制御なしで小規模システムに向くのが特徴。
注文サービス → "OrderCreated"イベント
↓
決済サービスが購読 → 決済完了 → "PaymentCompleted"
↓
在庫サービスが購読 → 在庫更新
2. Orchestration(オーケストレーション型)
Sagaオーケストレーターという中心の制御役が存在するモデルです。
各サービスを順に呼び出し、成功/失敗を監視して、失敗時は補償命令を発行するのが特徴。
[Saga Orchestrator]
↓
→ OrderService.create()
→ PaymentService.pay()
→ InventoryService.reduce()
もし、PaymentService が失敗したら
↓
→ PaymentService.compensate()
→ OrderService.cancel()
サンプルコード1: Choreography型
class SagaOrchestrator {
constructor(steps) {
this.steps = steps;
}
async execute() {
const completed = [];
try {
for (const step of this.steps) {
await step.action();
completed.push(step);
}
console.log("全ステップ成功");
} catch (e) {
console.log("エラー発生:", e.message);
for (const step of completed.reverse()) {
await step.compensate();
}
console.log("補償トランザクション完了");
}
}
}
const saga = new SagaOrchestrator([
{
action: async () => console.log("🛒 注文登録"),
compensate: async () => console.log("注文キャンセル")
},
{
action: async () => {
console.log("💳 決済処理");
throw new Error("決済失敗");
},
compensate: async () => console.log("決済取り消し")
},
{
action: async () => console.log("在庫更新"),
compensate: async () => console.log("在庫戻し")
}
]);
saga.execute();
実行結果
注文登録
決済処理
エラー発生: 決済失敗
注文キャンセル
補償トランザクション完了
メリット
・各サービスが独立してトランザクションを持てる。
・分散DBや2PC(2フェーズコミット)を避けられる。
・失敗時の回復処理を明示的に制御できる。
デメリット
・設計・実装が複雑(補償ロジックが多い)。
・一貫性は「最終的整合性(Eventually Consistent)」になる。
・監視・可視化が難しくなる(特にChoreography型)。
サンプルコード2: Orchestrator型
// ===== 各サービスの定義 =====
class OrderService {
async create() {
console.log("🛒 注文を登録");
}
async cancel() {
console.log("注文をキャンセル");
}
}
class PaymentService {
async pay() {
console.log("決済を実行");
throw new Error("決済失敗!"); // わざと失敗させる
}
async refund() {
console.log("決済を取り消し");
}
}
class InventoryService {
async reserve() {
console.log("在庫を確保");
}
async release() {
console.log("在庫を戻す");
}
}
// ===== オーケストレーター =====
class SagaOrchestrator {
constructor() {
this.steps = [];
}
addStep(action, compensation) {
this.steps.push({ action, compensation });
}
async execute() {
const completed = [];
try {
for (const step of this.steps) {
await step.action();
completed.push(step);
}
console.log("Saga 完了: すべて成功");
} catch (err) {
console.log("Saga エラー:", err.message);
console.log("補償トランザクション開始");
for (const step of completed.reverse()) {
await step.compensation();
}
console.log("Saga 補償完了");
}
}
}
// ===== 実行部分 =====
(async () => {
const order = new OrderService();
const payment = new PaymentService();
const inventory = new InventoryService();
const saga = new SagaOrchestrator();
saga.addStep(() => order.create(), () => order.cancel());
saga.addStep(() => payment.pay(), () => payment.refund());
saga.addStep(() => inventory.reserve(), () => inventory.release());
await saga.execute();
})();
実行結果
注文を登録
決済を実行
Saga エラー: 決済失敗!
補償トランザクション開始
注文をキャンセル
Saga 補償完了
ポイント解説
各サービスは「正常処理」と「補償処理」をセットで持つのがポイントです。
SagaOrchestrator がステップを順に実行します。
途中で失敗したら、完了済みのステップだけ逆順で補償を行う。
中央集権型(Orchestration)なので、状態管理・ロジック集中がしやすい特徴があります。
2つのモデル(型)の違いについて
| 比較項目 |
Choreography型 |
Orchestration型 |
| 制御方式 |
イベント連鎖 |
中央オーケストレーター |
| メリット |
疎結合で柔軟 |
状態を一元管理しやすい |
| デメリット |
可視化が難しい |
オーケストレーターが集中点になる |
| 適用規模 |
小~中規模 |
中~大規模(業務システム向け) |
あとがき
要するに、try~catchして、それをトランザクション処理する時に、分散システムの場合は、単純なトランザクションではなく、補填的なトランザクションをちゃんと設計しておいて、それを処理するのがSagaパターンということですね。
sagaって、「物語」という意味なので、ストーリーをちゃんと作ろうというパターンなのだと理解しました。
プログラミングを理解する時に、プログラムコードを読むという言い方をするけど、
難しいプログラムほど、小説を読むように、コードを読む感覚があります。
そんな時に、失敗をちゃんと補填できるような救済が書かれているコードは、行き届いているな〜と感じるでしょうね。
そんな感動するストーリーに仕上げることができるのが、このSagaパターンなんじゃないでしょうか?
0 件のコメント:
コメントを投稿