LESSON 25分

ストーリー

あなた
非同期通信
高橋アーキテクト
非同期通信をさらに押し進めたのが、イベント駆動アーキテクチャだ。サービスが直接呼び合うのではなく、“起きた出来事(イベント)“を中心にシステムが動く。これがマイクロサービスの本領を発揮する鍵だよ

イベント駆動アーキテクチャ(EDA)とは

サービスが直接他のサービスを呼び出すのではなく、「何が起きたか」というイベントを発行し、関心のあるサービスがそのイベントを購読して処理するアーキテクチャです。

リクエスト駆動(命令型):
  Order Service ──→ 在庫を減らせ ──→ Inventory Service
  Order Service ──→ 決済しろ ──→ Payment Service
  Order Service ──→ 通知を送れ ──→ Notification Service

イベント駆動(宣言型):
  Order Service ──→ 「注文が作成された」──→ [Event Bus]
                                           ├──→ Inventory Service(在庫を減らす)
                                           ├──→ Payment Service(決済を開始)
                                           └──→ Notification Service(通知を送る)

イベントの種類

1. ドメインイベント

ビジネスドメインで起きた事実を表すイベントです。

// ドメインイベントの例
interface OrderCreated {
  type: "order.created";
  data: {
    orderId: string;
    userId: string;
    items: Array<{ productId: string; quantity: number }>;
    totalAmount: number;
  };
  metadata: {
    eventId: string;       // イベントの一意なID
    timestamp: string;     // 発生時刻
    source: string;        // 発行元サービス
    correlationId: string; // リクエスト追跡ID
  };
}

interface PaymentCompleted {
  type: "payment.completed";
  data: {
    paymentId: string;
    orderId: string;
    amount: number;
    method: "credit_card" | "bank_transfer";
  };
  metadata: {
    eventId: string;
    timestamp: string;
    source: "payment-service";
    correlationId: string;
  };
}

2. 統合イベント

サービス間の連携のために発行されるイベントです。

3. イベント通知 vs イベント伝搬

// イベント通知: 「何が起きたか」だけ通知し、詳細はAPIで取得
interface OrderCreatedNotification {
  type: "order.created";
  data: { orderId: string }; // 最小限の情報
  // 受信側がAPI呼び出しで詳細を取得
}

// イベント伝搬: イベントに必要な情報をすべて含める
interface OrderCreatedCarrying {
  type: "order.created";
  data: {
    orderId: string;
    userId: string;
    items: Array<{ productId: string; quantity: number; price: number }>;
    totalAmount: number;
    shippingAddress: Address;
  };
  // 受信側はAPI呼び出し不要
}
方式メリットデメリット
イベント通知ペイロードが小さい追加のAPI呼び出しが必要(結合度UP)
イベント伝搬API呼び出し不要(疎結合)ペイロードが大きい、データの鮮度

イベント駆動のパターン

Publish-Subscribe(Pub/Sub)

// Publisher(発行者): イベントを発行
class OrderService {
  constructor(private eventBus: EventBus) {}

  async createOrder(data: OrderData): Promise<Order> {
    const order = await this.orderRepo.save(data);

    // イベントを発行(誰が購読するか知らない)
    await this.eventBus.publish("order.created", {
      orderId: order.id,
      userId: data.userId,
      items: data.items,
      totalAmount: data.totalAmount,
    });

    return order;
  }
}

// Subscriber(購読者): イベントを処理
class InventoryService {
  constructor(eventBus: EventBus) {
    // イベントを購読
    eventBus.subscribe("order.created", this.handleOrderCreated.bind(this));
  }

  async handleOrderCreated(event: OrderCreatedEvent): Promise<void> {
    for (const item of event.data.items) {
      await this.inventoryRepo.reserveStock(item.productId, item.quantity);
    }
  }
}

Event Sourcing

状態を保存する代わりに、状態変化のイベントをすべて記録します。

// Event Sourcing: イベントの履歴からステートを再構築
class OrderAggregate {
  private events: DomainEvent[] = [];
  private state: OrderState = { status: "DRAFT", items: [], total: 0 };

  // イベントを適用してステートを更新
  apply(event: DomainEvent): void {
    this.events.push(event);
    switch (event.type) {
      case "order.created":
        this.state = { ...this.state, status: "CREATED", items: event.data.items };
        break;
      case "order.paid":
        this.state = { ...this.state, status: "PAID" };
        break;
      case "order.shipped":
        this.state = { ...this.state, status: "SHIPPED" };
        break;
      case "order.cancelled":
        this.state = { ...this.state, status: "CANCELLED" };
        break;
    }
  }

  // イベント履歴からステートを再構築
  static fromEvents(events: DomainEvent[]): OrderAggregate {
    const aggregate = new OrderAggregate();
    events.forEach(e => aggregate.apply(e));
    return aggregate;
  }
}

イベント駆動のメリットとデメリット

メリットデメリット
サービス間の疎結合デバッグが困難
スケーラビリティが高いイベントの順序保証が難しい
新しい購読者の追加が容易結果整合性の受け入れ
障害の分離イベントストームのリスク
監査ログが自然に残る処理の全体像が見えにくい

まとめ

ポイント内容
EDAとはイベントを中心にサービスが連携するアーキテクチャ
イベントの種類ドメインイベント、統合イベント
2つの伝え方イベント通知 vs イベント伝搬
主要パターンPub/Sub、Event Sourcing

チェックリスト

  • リクエスト駆動とイベント駆動の違いを説明できる
  • ドメインイベントの構造を理解した
  • イベント通知とイベント伝搬の使い分けを判断できる
  • Event Sourcingの概要を理解した

次のステップへ

次はイベントを運ぶインフラ「メッセージブローカー」の選択について学びます。Kafka、RabbitMQ、SQSの特徴を比較しましょう。


推定読了時間: 25分