LESSON 40分

ストーリー

佐藤CTO
イベントはサービス間の”共通言語”だ
佐藤CTO
スキーマ設計を間違えると、全サービスがカオスになる。進化可能なスキーマが鍵だ

CloudEvents 仕様

CloudEventsはCNCFが策定したイベントデータの標準仕様です:

{
  "specversion": "1.0",
  "type": "com.shopmaster.order.confirmed",
  "source": "/order-service",
  "id": "evt-abc-123",
  "time": "2025-01-15T10:30:00Z",
  "datacontenttype": "application/json",
  "data": {
    "orderId": "ord-789",
    "customerId": "cust-456",
    "totalAmount": 15000,
    "currency": "JPY",
    "items": [
      { "productId": "prod-001", "quantity": 2, "price": 5000 },
      { "productId": "prod-002", "quantity": 1, "price": 5000 }
    ]
  }
}

メタデータ vs ペイロード

要素役割変更頻度
specversionCloudEvents バージョンほぼ変わらない
typeイベント種別(ルーティングに使用)新規追加のみ
source発行元サービス変わらない
id一意識別子(冪等性に使用)毎回異なる
dataビジネスペイロード頻繁に進化

スキーマレジストリ

graph LR
    P["Producer"] -->|スキーマ登録| SR["Schema Registry\nスキーマ互換性チェック"]
    SR -->|スキーマ検証| C["Consumer"]
    P -->|データ| K["Kafka"]
    K -->|データ| C

    style P fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style SR fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#92400e
    style C fill:#d1fae5,stroke:#059669,color:#065f46
    style K fill:#f3e8ff,stroke:#7c3aed,color:#5b21b6

ツール比較

ツール対応フォーマット特徴
Confluent Schema RegistryAvro, JSON Schema, ProtobufKafka統合、互換性チェック
AWS Glue Schema RegistryAvro, JSON SchemaAWS統合、サーバーレス
Apicurio RegistryOpenAPI, Avro, Protobuf, JSON SchemaOSS、多フォーマット

スキーマ互換性戦略

戦略許可される変更ユースケース
BACKWARDフィールド削除、デフォルト値追加新Consumer → 古Producer
FORWARDフィールド追加古Consumer → 新Producer
FULL追加+削除(デフォルト値あり)双方向互換
NONE任意の変更開発環境のみ
// FORWARD互換の例: フィールド追加は安全
// v1
interface OrderConfirmedV1 {
  orderId: string;
  totalAmount: number;
}

// v2: フィールド追加(古いConsumerは新フィールドを無視)
interface OrderConfirmedV2 {
  orderId: string;
  totalAmount: number;
  currency: string;      // 新規追加
  discountAmount?: number; // optional で追加
}

TypeScript でのスキーマ定義

import { z } from 'zod';

// イベントのベーススキーマ
const CloudEventBase = z.object({
  specversion: z.literal('1.0'),
  type: z.string(),
  source: z.string(),
  id: z.string().uuid(),
  time: z.string().datetime(),
  datacontenttype: z.literal('application/json'),
});

// 注文確定イベントのスキーマ
const OrderConfirmedEvent = CloudEventBase.extend({
  type: z.literal('order.confirmed'),
  data: z.object({
    orderId: z.string(),
    customerId: z.string(),
    totalAmount: z.number().positive(),
    currency: z.enum(['JPY', 'USD']),
    items: z.array(z.object({
      productId: z.string(),
      quantity: z.number().int().positive(),
      price: z.number().positive(),
    })),
  }),
});

type OrderConfirmedEvent = z.infer<typeof OrderConfirmedEvent>;

// Consumerでのバリデーション(Tolerant Reader)
function processEvent(raw: unknown): void {
  const result = OrderConfirmedEvent.safeParse(raw);
  if (!result.success) {
    console.warn('Unknown event format, skipping:', result.error);
    return; // 未知のフォーマットは無視(Tolerant Reader)
  }
  handleOrderConfirmed(result.data);
}

Event Storming からスキーマへ

ドメインイベント → コマンド → スキーマ定義
  「注文が確定された」    確定する     OrderConfirmed
  「決済が完了した」      決済する     PaymentCompleted
  「在庫が引当された」    引当する     StockReserved
  「出荷が開始された」    出荷する     ShipmentStarted

イベント命名規則

{ドメイン}.{エンティティ}.{過去形の動詞}
order.confirmed
payment.completed
inventory.reserved
shipment.started

まとめ

ポイント内容
CloudEvents標準仕様でイベントメタデータを統一
Schema Registryスキーマの登録・検証・互換性チェック
互換性戦略FORWARD互換でフィールド追加を安全に
Tolerant Reader未知のフィールドは無視して処理

チェックリスト

  • CloudEvents の基本構造を理解した
  • スキーマ互換性の4戦略を説明できる
  • Zod を使ったスキーマ定義とバリデーションを理解した
  • Event Storming からスキーマへの変換手順を理解した

次のステップへ

次は分散トランザクションを管理するSagaパターンを学びます。


推定読了時間: 40分