name: Event Style Guide
description: Event-driven design conventions: event envelope, naming, versioning, schema evolution rules, idempotency, ordering/partitioning, retry and dead-letter handling
Event Style Guide
Overview
มาตรฐานการออกแบบ events สำหรับ event-driven architecture: envelope format, naming, versioning, และ schema evolution ที่ทำให้ระบบ decouple ได้อย่างปลอดภัย
Why This Matters
- Interoperability: ทุก service เข้าใจ event format เดียวกัน
- Evolution: เปลี่ยน schema โดยไม่ break consumers
- Debugging: Trace events ข้าม services ได้
- Contract: Clear agreement between producers/consumers
Core Concepts
1. Event Envelope Structure
- ทุก event ต้องเป็น envelope เดียวกัน เพื่อให้ tooling/observability ทำงานได้
- metadata ต้องมี
id, type, source, time, specVersion
- correlation ต้องมี
correlationId (ผูกกับ request/trace) และ causationId (optional)
- payload อยู่ใน
data เท่านั้น (หลีกเลี่ยง “metadata ปน payload”)
2. Event Naming Convention
- รูปแบบ:
<domain>.<entity>.<action>.v<major>
- ใช้ past-tense สำหรับ domain events (
created, updated, completed)
- actions ต้องสื่อความหมายและไม่กว้างเกินไป (หลีกเลี่ยง
changed, processed ถ้าไม่จำเป็น)
3. Event Versioning
- version ใน
type เป็น major เพื่อบอก breaking changes: user.created.v1
- changes แบบ non-breaking (เพิ่ม field optional) ไม่ต้อง bump major แต่ควร track schema version ใน registry
- หากต้อง breaking: ออก
v2 และรองรับ consumers เก่าในช่วง deprecation
4. Schema Evolution
กฎหลัก: consumers ต้อง “ignore unknown fields” และ producers ต้องไม่ลบ/เปลี่ยนความหมาย field เดิมภายใน major เดียวกัน
- ✅ เพิ่ม field optional (หรือเพิ่ม enum values แบบ backward-compatible ถ้า consumers handle unknown)
- ❌ ลบ field, rename field, เปลี่ยน type, เพิ่ม required field
- ถ้าจำเป็นต้อง rename: ทำแบบ “add new + deprecate old” แล้วค่อยตัดใน major ถัดไป
5. Event Types
- Domain events: facts ที่เกิดขึ้นแล้ว (source-of-truth) เช่น
order.payment.completed.v1
- Integration events: แปล domain event สำหรับระบบอื่น/ภายนอก (อาจ mask/redact fields)
- Commands: “ขอให้ทำ” (ควรเป็น request/response หรือ separate channel) ไม่ควรปนกับ domain events
6. Idempotency
id ต้อง unique (UUID v4) และใช้สำหรับ dedupe ที่ consumer
- consumer ต้อง idempotent: บันทึก
eventId ที่ process แล้ว หรือใช้ upsert/deterministic state transitions
- ถ้า broker อาจส่งซ้ำ (at-least-once) ให้คาดไว้ตั้งแต่แรก
7. Event Ordering
- ถ้าต้องการ ordering ให้ define partition key (เช่น
tenantId, orderId) และส่งไป partition เดียวกัน
- ถ้าต้อง strict ordering ต่อ entity ให้มี
sequence (optional) ใน data หรือ details
- หลีกเลี่ยงการพึ่ง ordering ข้าม partition (แทบไม่รับประกัน)
8. Dead Letter Handling
- define retry policy: exponential backoff + jitter + max attempts
- classify errors: retryable vs non-retryable (validation/domain errors ไม่ควร retry)
- เมื่อเกิน max retries ให้ส่ง DLQ พร้อม metadata ที่พอสำหรับ replay (แต่ไม่ใส่ secrets/PII เกินจำเป็น)
- ต้องมี playbook: “inspect → fix → replay”
Quick Start
export interface EventEnvelope<T> {
id: string;
type: string;
source: string;
specVersion: "1.0";
time: string;
correlationId: string;
causationId?: string;
dataContentType: "application/json";
dataSchema?: string;
data: T;
}
export type UserCreatedV1 = EventEnvelope<{
userId: string;
email: string;
tenantId: string;
}>;
Production Checklist
Event Envelope Standard
interface EventEnvelope<T> {
// Metadata
id: string; // UUID v4
type: string; // "user.created.v1"
source: string; // "user-service"
specVersion: string; // "1.0"
time: string; // ISO 8601
// Correlation
correlationId: string; // Request correlation
causationId?: string; // Causing event ID
// Payload
data: T; // Event-specific data
dataContentType: string; // "application/json"
dataSchema?: string; // Schema URL
}
Retry & DLQ Policy (ตัวอย่าง)
- retry: 1s, 5s, 15s, 1m, 5m (max 5 ครั้ง) + jitter
- non-retryable:
VAL_, BIZ_, AUTH_, AUTHZ_ (ส่ง DLQ ทันทีหรือ discard ตาม policy)
- DLQ message ต้องมี: original
event.id, event.type, error summary, attempt count, first/last seen timestamp
Event Naming Convention
# Format: <domain>.<entity>.<action>.v<version>
# Examples:
user.account.created.v1
order.payment.completed.v1
inventory.stock.reserved.v1
notification.email.sent.v1
# Actions (past tense for domain events):
- created, updated, deleted
- completed, failed, cancelled
- started, finished, expired
Schema Evolution Rules
| Change Type |
Compatibility |
Example |
| Add optional field |
✅ Safe |
Add middleName?: string |
| Add required field |
❌ Breaking |
Add email: string |
| Remove field |
❌ Breaking |
Remove age |
| Rename field |
❌ Breaking |
name → fullName |
| Change type |
❌ Breaking |
age: string → age: number |
Anti-patterns
- No versioning: Can't evolve schemas
- Massive payloads: Events too large
- Missing correlation: Can't trace flows
- Shared events: Tight coupling between services
- Hidden contracts: schema อยู่ใน code แต่ไม่มี registry/docs
Integration Points
- Message brokers (Kafka, RabbitMQ)
- Schema registries
- Event stores
- Monitoring systems
Further Reading
1---2name: event-style-guide3description: มาตรฐานการออกแบบ events สำหรับ event-driven architecture: envelope format, naming, versioning, และ schema evolution ที่ทำให้ระบบ decouple ได้อย่างปลอดภัย4---5
6---
7name: Event Style Guide
8description: Event-driven design conventions: event envelope, naming, versioning, schema evolution rules, idempotency, ordering/partitioning, retry and dead-letter handling
9---
10
11# Event Style Guide
12
13## Overview
14
15มาตรฐานการออกแบบ events สำหรับ event-driven architecture: envelope format, naming, versioning, และ schema evolution ที่ทำให้ระบบ decouple ได้อย่างปลอดภัย
16
17## Why This Matters
18
19- **Interoperability**: ทุก service เข้าใจ event format เดียวกัน
20- **Evolution**: เปลี่ยน schema โดยไม่ break consumers
21- **Debugging**: Trace events ข้าม services ได้
22- **Contract**: Clear agreement between producers/consumers
23
24---
25
26## Core Concepts
27
28### 1. Event Envelope Structure
29
30- ทุก event ต้องเป็น envelope เดียวกัน เพื่อให้ tooling/observability ทำงานได้
31- metadata ต้องมี `id`, `type`, `source`, `time`, `specVersion`
32- correlation ต้องมี `correlationId` (ผูกกับ request/trace) และ `causationId` (optional)
33- payload อยู่ใน `data` เท่านั้น (หลีกเลี่ยง “metadata ปน payload”)
34
35### 2. Event Naming Convention
36
37- รูปแบบ: `<domain>.<entity>.<action>.v<major>`
38- ใช้ past-tense สำหรับ domain events (`created`, `updated`, `completed`)
39- actions ต้องสื่อความหมายและไม่กว้างเกินไป (หลีกเลี่ยง `changed`, `processed` ถ้าไม่จำเป็น)
40
41### 3. Event Versioning
42
43- version ใน `type` เป็น **major** เพื่อบอก breaking changes: `user.created.v1`
44- changes แบบ non-breaking (เพิ่ม field optional) ไม่ต้อง bump major แต่ควร track schema version ใน registry
45- หากต้อง breaking: ออก `v2` และรองรับ consumers เก่าในช่วง deprecation
46
47### 4. Schema Evolution
48
49กฎหลัก: consumers ต้อง “ignore unknown fields” และ producers ต้องไม่ลบ/เปลี่ยนความหมาย field เดิมภายใน major เดียวกัน
50
51- ✅ เพิ่ม field optional (หรือเพิ่ม enum values แบบ backward-compatible ถ้า consumers handle unknown)
52- ❌ ลบ field, rename field, เปลี่ยน type, เพิ่ม required field
53- ถ้าจำเป็นต้อง rename: ทำแบบ “add new + deprecate old” แล้วค่อยตัดใน major ถัดไป
54
55### 5. Event Types
56
57- **Domain events**: facts ที่เกิดขึ้นแล้ว (source-of-truth) เช่น `order.payment.completed.v1`
58- **Integration events**: แปล domain event สำหรับระบบอื่น/ภายนอก (อาจ mask/redact fields)
59- **Commands**: “ขอให้ทำ” (ควรเป็น request/response หรือ separate channel) ไม่ควรปนกับ domain events
60
61### 6. Idempotency
62
63- `id` ต้อง unique (UUID v4) และใช้สำหรับ dedupe ที่ consumer
64- consumer ต้อง idempotent: บันทึก `eventId` ที่ process แล้ว หรือใช้ upsert/deterministic state transitions
65- ถ้า broker อาจส่งซ้ำ (at-least-once) ให้คาดไว้ตั้งแต่แรก
66
67### 7. Event Ordering
68
69- ถ้าต้องการ ordering ให้ define partition key (เช่น `tenantId`, `orderId`) และส่งไป partition เดียวกัน
70- ถ้าต้อง strict ordering ต่อ entity ให้มี `sequence` (optional) ใน `data` หรือ `details`
71- หลีกเลี่ยงการพึ่ง ordering ข้าม partition (แทบไม่รับประกัน)
72
73### 8. Dead Letter Handling
74
75- define retry policy: exponential backoff + jitter + max attempts
76- classify errors: retryable vs non-retryable (validation/domain errors ไม่ควร retry)
77- เมื่อเกิน max retries ให้ส่ง DLQ พร้อม metadata ที่พอสำหรับ replay (แต่ไม่ใส่ secrets/PII เกินจำเป็น)
78- ต้องมี playbook: “inspect → fix → replay”
79
80## Quick Start
81
82```typescript
83export interface EventEnvelope<T> {
84 id: string;
85 type: string;
86 source: string;
87 specVersion: "1.0";
88 time: string;
89
90 correlationId: string;
91 causationId?: string;
92
93 dataContentType: "application/json";
94 dataSchema?: string;
95 data: T;
96}
97
98export type UserCreatedV1 = EventEnvelope<{
99 userId: string;
100 email: string;
101 tenantId: string;
102}>;
103```
104
105## Production Checklist
106
107- [ ] All events follow envelope standard
108- [ ] Event naming consistent
109- [ ] Version in every event
110- [ ] Schema registered in registry
111- [ ] Idempotency keys present
112- [ ] Dead letter queue configured
113
114## Event Envelope Standard
115
116```typescript
117interface EventEnvelope<T> {
118 // Metadata
119 id: string; // UUID v4
120 type: string; // "user.created.v1"
121 source: string; // "user-service"
122 specVersion: string; // "1.0"
123 time: string; // ISO 8601
124
125 // Correlation
126 correlationId: string; // Request correlation
127 causationId?: string; // Causing event ID
128
129 // Payload
130 data: T; // Event-specific data
131 dataContentType: string; // "application/json"
132 dataSchema?: string; // Schema URL
133}
134```
135
136## Retry & DLQ Policy (ตัวอย่าง)
137
138- retry: 1s, 5s, 15s, 1m, 5m (max 5 ครั้ง) + jitter
139- non-retryable: `VAL_`, `BIZ_`, `AUTH_`, `AUTHZ_` (ส่ง DLQ ทันทีหรือ discard ตาม policy)
140- DLQ message ต้องมี: original `event.id`, `event.type`, error summary, attempt count, first/last seen timestamp
141
142## Event Naming Convention
143
144```
145# Format: <domain>.<entity>.<action>.v<version>
146
147# Examples:
148user.account.created.v1
149order.payment.completed.v1
150inventory.stock.reserved.v1
151notification.email.sent.v1
152
153# Actions (past tense for domain events):
154- created, updated, deleted
155- completed, failed, cancelled
156- started, finished, expired
157```
158
159## Schema Evolution Rules
160
161| Change Type | Compatibility | Example |
162|-------------|---------------|---------|
163| Add optional field | ✅ Safe | Add `middleName?: string` |
164| Add required field | ❌ Breaking | Add `email: string` |
165| Remove field | ❌ Breaking | Remove `age` |
166| Rename field | ❌ Breaking | `name` → `fullName` |
167| Change type | ❌ Breaking | `age: string` → `age: number` |
168
169## Anti-patterns
170
1711. **No versioning**: Can't evolve schemas
1722. **Massive payloads**: Events too large
1733. **Missing correlation**: Can't trace flows
1744. **Shared events**: Tight coupling between services
1755. **Hidden contracts**: schema อยู่ใน code แต่ไม่มี registry/docs
176
177## Integration Points
178
179- Message brokers (Kafka, RabbitMQ)
180- Schema registries
181- Event stores
182- Monitoring systems
183
184## Further Reading
185
186- [CloudEvents Specification](https://cloudevents.io/)
187- [Event-Driven Architecture Patterns](https://www.confluent.io/blog/)
188- [Schema Evolution Best Practices](https://docs.confluent.io/platform/current/schema-registry/)