EXERCISE 60分

ストーリー

佐藤CTO
サービス間の通信設計は、アーキテクチャの成否を決める
佐藤CTO
イベント駆動で疎結合を実現する設計を、自分の手で作ってみよう

ミッション概要

ミッションテーマ目安時間
Mission 1注文フローのイベントスキーマ設計15分
Mission 2Choreography Saga の設計15分
Mission 3Orchestration Saga の設計15分
Mission 4障害シナリオの対応設計15分

前提シナリオ

ShopMasterの注文フローをイベント駆動で設計します。関連サービス:注文、在庫、決済、配送、通知の5サービス。


Mission 1: 注文フローのイベントスキーマ設計(15分)

要件

注文確定から配送完了までのドメインイベントをCloudEvents仕様で定義してください。

解答例
// 1. 注文確定
const orderConfirmed = {
  specversion: '1.0',
  type: 'order.confirmed',
  source: '/order-service',
  id: 'evt-001',
  time: '2025-01-15T10:00:00Z',
  data: {
    orderId: 'ord-789',
    customerId: 'cust-456',
    items: [{ productId: 'prod-001', quantity: 2, unitPrice: 5000 }],
    totalAmount: 10000,
    currency: 'JPY',
    shippingAddress: { prefecture: '東京都', city: '渋谷区' },
  },
};

// 2. 在庫引当完了
const stockReserved = {
  type: 'inventory.stock.reserved',
  data: {
    orderId: 'ord-789',
    reservationId: 'res-001',
    items: [{ productId: 'prod-001', quantity: 2, reserved: true }],
    expiresAt: '2025-01-15T10:15:00Z',
  },
};

// 3. 決済完了
const paymentCompleted = {
  type: 'payment.completed',
  data: {
    orderId: 'ord-789',
    paymentId: 'pay-001',
    amount: 10000,
    method: 'credit_card',
  },
};

// 4. 出荷開始
const shipmentStarted = {
  type: 'shipping.shipment.started',
  data: {
    orderId: 'ord-789',
    shipmentId: 'ship-001',
    trackingNumber: 'TRK-12345',
    carrier: 'yamato',
    estimatedDelivery: '2025-01-17',
  },
};

// 5. 配送完了
const shipmentDelivered = {
  type: 'shipping.shipment.delivered',
  data: {
    orderId: 'ord-789',
    shipmentId: 'ship-001',
    deliveredAt: '2025-01-17T14:30:00Z',
  },
};

Mission 2: Choreography Saga の設計(15分)

要件

注文フローをChoreography Sagaで設計し、各サービスが購読するイベントと発行するイベントを定義してください。

解答例
サービス購読イベント処理発行イベント
注文(API: 注文リクエスト)注文レコード作成order.confirmed
在庫order.confirmed在庫引当inventory.stock.reserved / inventory.stock.insufficient
決済inventory.stock.reserved決済処理payment.completed / payment.failed
配送payment.completed出荷手配shipping.shipment.started
通知全イベントメール/プッシュ送信(なし)
注文payment.completedステータス更新 → PAID(状態更新のみ)
注文shipping.shipment.startedステータス更新 → SHIPPED(状態更新のみ)

補償フロー:

失敗イベント補償アクション
inventory.stock.insufficientorder.cancelled を発行
payment.failedinventory.stock.released を発行→ order.cancelled

Mission 3: Orchestration Saga の設計(15分)

要件

同じ注文フローをOrchestration Sagaで設計し、オーケストレーターの状態遷移図を作成してください。

解答例
graph TD
    S["STARTED"] --> RS["RESERVING_STOCK"]
    RS --> SR["STOCK_RESERVED"]
    RS --> SI["STOCK_INSUFFICIENT"]
    SR --> CP["CHARGING_PAYMENT"]
    SI --> C1["CANCELLED"]
    CP --> PC["PAYMENT_CHARGED"]
    CP --> PF["PAYMENT_FAILED"]
    PC --> AS["ARRANGING_SHIPMENT"]
    PF --> CS["COMPENSATING_STOCK"]
    AS --> SA["SHIPMENT_ARRANGED"]
    CS --> C2["CANCELLED"]
    SA --> Done["COMPLETED"]

    style S fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
    style RS fill:#fef3c7,stroke:#d97706,color:#92400e
    style SR fill:#d1fae5,stroke:#059669,color:#065f46
    style SI fill:#fee2e2,stroke:#dc2626,color:#991b1b
    style CP fill:#fef3c7,stroke:#d97706,color:#92400e
    style C1 fill:#fee2e2,stroke:#dc2626,color:#991b1b
    style PC fill:#d1fae5,stroke:#059669,color:#065f46
    style PF fill:#fee2e2,stroke:#dc2626,color:#991b1b
    style AS fill:#fef3c7,stroke:#d97706,color:#92400e
    style CS fill:#fee2e2,stroke:#dc2626,color:#991b1b
    style SA fill:#d1fae5,stroke:#059669,color:#065f46
    style C2 fill:#fee2e2,stroke:#dc2626,color:#991b1b
    style Done fill:#d1fae5,stroke:#059669,stroke-width:3px,color:#065f46
// Orchestrator実装
class OrderSagaOrchestrator {
  async handle(state: SagaState, event: DomainEvent): Promise<SagaState> {
    switch (state.status) {
      case 'STARTED':
        await this.inventoryClient.reserveStock(state.orderId, state.items);
        return { ...state, status: 'RESERVING_STOCK' };

      case 'RESERVING_STOCK':
        if (event.type === 'inventory.stock.reserved') {
          await this.paymentClient.charge(state.orderId, state.amount);
          return { ...state, status: 'CHARGING_PAYMENT' };
        }
        if (event.type === 'inventory.stock.insufficient') {
          await this.orderClient.cancel(state.orderId);
          return { ...state, status: 'CANCELLED' };
        }
        break;

      case 'CHARGING_PAYMENT':
        if (event.type === 'payment.completed') {
          await this.shippingClient.arrange(state.orderId);
          return { ...state, status: 'ARRANGING_SHIPMENT' };
        }
        if (event.type === 'payment.failed') {
          await this.inventoryClient.releaseStock(state.orderId);
          return { ...state, status: 'COMPENSATING_STOCK' };
        }
        break;
    }
    return state;
  }
}

Mission 4: 障害シナリオの対応設計(15分)

要件

以下の3つの障害シナリオに対する対応を設計してください。

  1. 決済サービスがタイムアウト(応答なし)
  2. 配送手配後に顧客がキャンセル要求
  3. 在庫引当後にプロダクトが値上げされた
解答例

シナリオ1: 決済タイムアウト

対応: リトライ + タイムアウト後に補償
1. 30秒タイムアウト + 3回リトライ(指数バックオフ)
2. 全リトライ失敗 → 在庫引当を解除(compensate)
3. 注文ステータスを「決済失敗」に更新
4. 顧客に通知「決済処理に失敗しました。再度お試しください」
5. 冪等キーで重複決済を防止

シナリオ2: 配送手配後のキャンセル

対応: 逆順の補償トランザクション
1. 配送キャンセル → shipping.cancelled
2. 決済返金 → payment.refunded
3. 在庫引当解除 → inventory.released
4. 注文キャンセル → order.cancelled
注意: 配送が既に出荷済みの場合は返品フローに分岐

シナリオ3: 引当後の値上げ

対応: 注文確定時の価格を記録(イベントソーシング)
- 注文イベントに確定時の価格を含める
- 在庫引当は数量のみ管理(価格は注文サービスの責務)
- 値上げは新規注文にのみ適用
- 既存注文は確定時価格を保証(契約)

まとめ

ポイント内容
イベントスキーマCloudEvents 仕様で統一
Choreography自律型、小規模向き
Orchestration集中管理、大規模向き
障害対応リトライ+補償+冪等性で堅牢化

チェックリスト

  • CloudEvents仕様でイベントスキーマを設計できた
  • Choreography Saga を設計できた
  • Orchestration Saga を状態遷移図で設計できた
  • 障害シナリオの対応を設計できた

次のステップへ

次はチェックポイントクイズでイベント駆動アーキテクチャの理解度を確認します。


推定読了時間: 60分