ストーリー
佐
佐藤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 ペイロード
| 要素 | 役割 | 変更頻度 |
|---|
| specversion | CloudEvents バージョン | ほぼ変わらない |
| 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 Registry | Avro, JSON Schema, Protobuf | Kafka統合、互換性チェック |
| AWS Glue Schema Registry | Avro, JSON Schema | AWS統合、サーバーレス |
| Apicurio Registry | OpenAPI, Avro, Protobuf, JSON Schema | OSS、多フォーマット |
スキーマ互換性戦略
| 戦略 | 許可される変更 | ユースケース |
|---|
| 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 | 未知のフィールドは無視して処理 |
チェックリスト
次のステップへ
次は分散トランザクションを管理するSagaパターンを学びます。
推定読了時間: 40分