LESSON 40分

ストーリー

高橋アーキテクト
Sagaを実装するぞ!
高橋アーキテクト
Sagaには2つのスタイルがある。ダンスのように各サービスが自律的に動く”Choreography”と、指揮者が全体を管理する”Orchestration”だ。どちらを選ぶかは、システムの複雑さとチーム構成で変わる

Choreography(コレオグラフィ)

各サービスがイベントを発行・購読し、自律的に協調するスタイルです。中央の制御者はいません。

注文フロー(Choreography):

[Order]──publish──→ order.created

                   [Payment]──subscribe──→ order.created

                   [Payment]──publish──→ payment.completed

                   [Inventory]──subscribe──→ payment.completed

                   [Inventory]──publish──→ stock.reserved

                   [Shipping]──subscribe──→ stock.reserved

                   [Shipping]──publish──→ shipment.created

実装例

// Order Service
class OrderService {
  async createOrder(data: OrderData): Promise<Order> {
    const order = await this.orderRepo.save({ ...data, status: "PENDING" });
    await this.eventBus.publish("order.created", { orderId: order.id, ...data });
    return order;
  }

  // 決済失敗イベントを購読 → 補償
  async handlePaymentFailed(event: PaymentFailedEvent): Promise<void> {
    await this.orderRepo.updateStatus(event.data.orderId, "CANCELLED");
  }
}

// Payment Service
class PaymentService {
  // 注文作成イベントを購読
  async handleOrderCreated(event: OrderCreatedEvent): Promise<void> {
    try {
      const payment = await this.paymentGateway.charge(event.data.totalAmount);
      await this.eventBus.publish("payment.completed", {
        orderId: event.data.orderId,
        paymentId: payment.id,
      });
    } catch (error) {
      await this.eventBus.publish("payment.failed", {
        orderId: event.data.orderId,
        reason: error.message,
      });
    }
  }
}

// Inventory Service
class InventoryService {
  // 決済完了イベントを購読
  async handlePaymentCompleted(event: PaymentCompletedEvent): Promise<void> {
    try {
      await this.reserveStock(event.data.orderId);
      await this.eventBus.publish("stock.reserved", {
        orderId: event.data.orderId,
      });
    } catch (error) {
      await this.eventBus.publish("stock.insufficient", {
        orderId: event.data.orderId,
        reason: error.message,
      });
      // → Payment Serviceが返金イベントを発行
    }
  }
}

Choreographyの補償フロー

正常系:
  order.created → payment.completed → stock.reserved → shipment.created

異常系(在庫不足):
  order.created → payment.completed → stock.insufficient

                                     payment.refunded ← Payment が返金

                                     order.cancelled ← Order がキャンセル

Orchestration(オーケストレーション)

中央のOrchestrator(指揮者)がSagaの全体フローを管理します。

注文フロー(Orchestration):

                  [Order Saga Orchestrator]

                    1. createOrder()

                    2. charge()──→ [Payment Service]
                         │               │
                    3. reserve()──→ [Inventory Service]
                         │               │
                    4. ship()────→ [Shipping Service]

                    完了 or 補償開始

実装例

// Saga Orchestrator
class OrderSagaOrchestrator {
  private steps: SagaStep[] = [];

  constructor(
    private orderService: OrderService,
    private paymentService: PaymentService,
    private inventoryService: InventoryService,
    private shippingService: ShippingService
  ) {
    this.steps = [
      {
        name: "createOrder",
        execute: (ctx) => this.orderService.create(ctx.orderData),
        compensate: (ctx) => this.orderService.cancel(ctx.orderId),
      },
      {
        name: "chargePayment",
        execute: (ctx) => this.paymentService.charge(ctx.orderId, ctx.amount),
        compensate: (ctx) => this.paymentService.refund(ctx.paymentId),
      },
      {
        name: "reserveStock",
        execute: (ctx) => this.inventoryService.reserve(ctx.orderId, ctx.items),
        compensate: (ctx) => this.inventoryService.release(ctx.orderId),
      },
      {
        name: "createShipment",
        execute: (ctx) => this.shippingService.create(ctx.orderId),
        compensate: (ctx) => this.shippingService.cancel(ctx.shipmentId),
      },
    ];
  }

  async execute(orderData: OrderData): Promise<SagaResult> {
    const context: SagaContext = { orderData };
    const completedSteps: SagaStep[] = [];

    for (const step of this.steps) {
      try {
        const result = await step.execute(context);
        Object.assign(context, result); // 結果をコンテキストに追加
        completedSteps.push(step);
      } catch (error) {
        // 失敗 → 補償を逆順で実行
        console.error(`Step "${step.name}" failed: ${error.message}`);
        await this.compensate(completedSteps, context);
        return { success: false, error: error.message };
      }
    }

    return { success: true, orderId: context.orderId };
  }

  private async compensate(
    completedSteps: SagaStep[],
    context: SagaContext
  ): Promise<void> {
    // 逆順で補償を実行
    for (const step of completedSteps.reverse()) {
      try {
        await step.compensate(context);
      } catch (error) {
        // 補償の失敗はログに記録して手動対応
        console.error(`Compensation "${step.name}" failed: ${error.message}`);
        await this.alertOps(step.name, error);
      }
    }
  }
}

Orchestratorの状態管理

// Sagaの状態を永続化
interface SagaState {
  sagaId: string;
  type: "ORDER_SAGA";
  currentStep: number;
  status: "IN_PROGRESS" | "COMPLETED" | "COMPENSATING" | "FAILED";
  context: Record<string, unknown>;
  steps: Array<{
    name: string;
    status: "PENDING" | "COMPLETED" | "COMPENSATED" | "FAILED";
    result?: unknown;
  }>;
  createdAt: Date;
  updatedAt: Date;
}

// 状態をDBに保存して、クラッシュ後も復旧可能に

比較

観点ChoreographyOrchestration
制御方式分散(イベント駆動)集中(Orchestrator)
結合度低い(イベントのみ)中程度(Orchestratorが全サービスを知る)
フロー可視性低い(イベントを追跡)高い(Orchestratorのコードに集約)
複雑さの増加サービス追加時にイベント関係が複雑化Orchestratorのロジックが複雑化
単一障害点なしOrchestratorがリスク
デバッグ困難比較的容易
適切な場面3〜5ステップの単純なフロー5ステップ以上の複雑なフロー

選択のガイドライン

const selectionGuide = {
  choreography: {
    when: [
      "ステップ数が少ない(3〜5)",
      "各サービスチームが独立して開発",
      "イベント駆動が既に導入済み",
      "循環的な依存がない",
    ],
    avoid: "ステップが多い、フロー変更が頻繁",
  },
  orchestration: {
    when: [
      "ステップ数が多い(5以上)",
      "複雑な条件分岐がある",
      "フローの可視性が重要",
      "監視・デバッグの容易さが必要",
    ],
    avoid: "サービス間の独立性を最大化したい場合",
  },
};

まとめ

ポイント内容
Choreography自律分散、イベント駆動、疎結合
Orchestration中央集権、フロー可視、デバッグ容易
選択基準ステップ数、複雑さ、チーム構成
共通の課題補償トランザクションの設計

チェックリスト

  • Choreographyの動作フローを説明できる
  • Orchestrationの動作フローを説明できる
  • 両者のメリット・デメリットを比較できる
  • プロジェクトに応じた選択ができる

次のステップへ

次は両方のスタイルに共通する「補償トランザクション」を深掘りします。失敗時にどうやってシステムを整合状態に戻すかを学びましょう。


推定読了時間: 40分