ストーリー
なぜSagaが必要か
モノリスでは1つのDBトランザクションで整合性を保証できました:
// モノリス: 単一トランザクション
await db.transaction(async (tx) => {
await tx.orders.create(order);
await tx.inventory.reserve(items);
await tx.payments.charge(payment);
// 全て成功 or 全てロールバック
});
マイクロサービスでは各サービスが独自DBを持つため、分散トランザクションが必要です。
Choreography Saga vs Orchestration Saga
Choreography(振付型)
各サービスが自律的にイベントを発行・購読:
sequenceDiagram
participant Order as 注文Service
participant Stock as 在庫Service
participant Pay as 決済Service
participant Ship as 配送Service
Order->>Stock: OrderCreated
Stock->>Pay: StockReserved
Pay->>Ship: PaymentCharged
Ship-->>Order: ShipmentCreated
Orchestration(指揮者型)
中央のオーケストレーターが全体を制御:
graph TD
Orch["Saga Orchestrator"] -->|"① 注文作成"| OrderSvc["注文 Svc"]
Orch -->|"② 在庫引当"| StockSvc["在庫 Svc"]
Orch -->|"③ 決済請求"| PaySvc["決済 Svc"]
Orch -->|"④ 配送手配"| ShipSvc["配送 Svc"]
style Orch fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#5b21b6
style OrderSvc fill:#dbeafe,stroke:#2563eb,color:#1e40af
style StockSvc fill:#d1fae5,stroke:#059669,color:#065f46
style PaySvc fill:#fef3c7,stroke:#d97706,color:#92400e
style ShipSvc fill:#fee2e2,stroke:#dc2626,color:#991b1b
比較
| 項目 | Choreography | Orchestration |
|---|---|---|
| 結合度 | 低(イベントのみ) | 中(オーケストレーター) |
| 可視性 | 低(追跡困難) | 高(一元管理) |
| 複雑さ | サービス増で急増 | オーケストレーターに集中 |
| 適用場面 | 3-4サービス以下 | 5サービス以上 |
| 単一障害点 | なし | オーケストレーター |
補償トランザクション
Sagaの各ステップには「補償(undo)」アクションを定義します:
正常フロー:
注文作成 → 在庫引当 → 決済処理 → 配送手配
補償フロー(決済失敗時):
配送手配 ← 決済処理(失敗) ← 在庫引当解除 ← 注文キャンセル
// Sagaステップの定義
interface SagaStep<T> {
name: string;
execute: (context: T) => Promise<void>;
compensate: (context: T) => Promise<void>;
}
const orderSagaSteps: SagaStep<OrderContext>[] = [
{
name: 'createOrder',
execute: async (ctx) => {
ctx.orderId = await orderService.create(ctx.orderData);
},
compensate: async (ctx) => {
await orderService.cancel(ctx.orderId);
},
},
{
name: 'reserveStock',
execute: async (ctx) => {
await inventoryService.reserve(ctx.orderId, ctx.items);
},
compensate: async (ctx) => {
await inventoryService.release(ctx.orderId);
},
},
{
name: 'chargePayment',
execute: async (ctx) => {
ctx.paymentId = await paymentService.charge(ctx.payment);
},
compensate: async (ctx) => {
await paymentService.refund(ctx.paymentId);
},
},
];
Saga Orchestrator の実装
class SagaOrchestrator<T> {
private completedSteps: SagaStep<T>[] = [];
async execute(steps: SagaStep<T>[], context: T): Promise<void> {
for (const step of steps) {
try {
await step.execute(context);
this.completedSteps.push(step);
} catch (error) {
console.error(`Saga failed at step: ${step.name}`, error);
await this.compensate(context);
throw new SagaFailedError(step.name, error);
}
}
}
private async compensate(context: T): Promise<void> {
// 完了したステップを逆順に補償
for (const step of this.completedSteps.reverse()) {
try {
await step.compensate(context);
} catch (error) {
// 補償失敗はアラート + 手動対応キューへ
console.error(`Compensation failed: ${step.name}`, error);
await this.alertManualIntervention(step, error);
}
}
}
}
// 使用例
const orchestrator = new SagaOrchestrator<OrderContext>();
await orchestrator.execute(orderSagaSteps, {
orderData: { customerId: 'cust-1', items: [...] },
payment: { method: 'credit_card', amount: 15000 },
});
Saga設計のポイント
| 原則 | 説明 |
|---|---|
| 冪等性 | 各ステップは何度実行しても同じ結果 |
| ピボットトランザクション | 補償不可能なステップは最後に実行 |
| リトライ可能 | 一時的な失敗はリトライで対応 |
| タイムアウト | 各ステップにタイムアウトを設定 |
| 状態永続化 | Saga の進行状態をDBに保存 |
まとめ
| ポイント | 内容 |
|---|---|
| Choreography | イベント駆動の自律的な協調 |
| Orchestration | 中央制御で可視性が高い |
| 補償トランザクション | 失敗時にビジネスレベルでundo |
| 冪等性 | 分散環境での信頼性の基盤 |
チェックリスト
- Choreography と Orchestration の違いを説明できる
- 補償トランザクションを設計できる
- Saga Orchestrator の基本構造を理解した
- 冪等性の重要性を理解した
次のステップへ
次は演習でイベント駆動アーキテクチャを設計します。
推定読了時間: 40分