--- id: wiki-2026-0508-event-sourcing-pattern title: Event Sourcing Pattern category: 10_Wiki/Topics status: verified canonical_id: self aliases: [event sourcing, ES, append-only, event store, projection, snapshot] duplicate_of: none source_trust_level: A confidence_score: 0.95 verification_status: applied tags: [architecture, event-sourcing, cqrs, domain-driven-design, audit, kafka] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript / Java framework: EventStoreDB / Kafka / Axon --- # Event Sourcing ## 매 한 줄 > **"매 state 의 store 의 X — 매 event 의 sequence 의 store"**. 매 current state = 매 replay 의 result. 매 audit + 매 temporal query + 매 CQRS 의 partner. 매 modern: 매 Kafka, EventStoreDB, Axon. ## 매 핵심 ### 매 vs CRUD - **CRUD**: 매 row 의 update. - **ES**: 매 immutable event 의 append. - **Projection**: 매 event → 매 read model. ### 매 component - **Event store**: 매 append-only. - **Aggregate**: 매 command → events. - **Projection / read model**: 매 query. - **Snapshot**: 매 replay 최적화. - **Outbox**: 매 publish reliability. ### 매 응용 1. **Banking**: 매 audit. 2. **E-commerce**: 매 order lifecycle. 3. **Compliance**: 매 immutable history. 4. **Collaboration**: 매 Figma. 5. **Game state**: 매 replay. ### 매 trade-off - ✅ Audit, replay, temporal query, decouple. - ❌ Complexity, eventual consistency, schema evolution. ## 💻 패턴 ### Event store (TypeScript) ```typescript type DomainEvent = { aggregateId: string; type: string; payload: object; version: number; timestamp: Date }; class EventStore { private events: DomainEvent[] = []; append(event: DomainEvent) { const last = this.events.filter(e => e.aggregateId === event.aggregateId).pop(); const expectedVersion = (last?.version ?? 0) + 1; if (event.version !== expectedVersion) throw new Error('Concurrency conflict'); this.events.push(event); } load(aggregateId: string): DomainEvent[] { return this.events.filter(e => e.aggregateId === aggregateId); } } ``` ### Aggregate (replay) ```typescript class Order { state = { id: '', status: 'NEW', items: [] as any[], version: 0 }; static fromHistory(events: DomainEvent[]): Order { const o = new Order(); events.forEach(e => o.apply(e)); return o; } apply(event: DomainEvent) { switch (event.type) { case 'OrderCreated': this.state.id = event.payload.id; break; case 'ItemAdded': this.state.items.push(event.payload); break; case 'OrderPlaced': this.state.status = 'PLACED'; break; } this.state.version = event.version; } addItem(item: any): DomainEvent { if (this.state.status !== 'NEW') throw new Error('Cannot modify'); const event = { aggregateId: this.state.id, type: 'ItemAdded', payload: item, version: this.state.version + 1, timestamp: new Date() }; this.apply(event); return event; } } ``` ### Projection ```typescript class OrderListProjection { private orders = new Map(); handle(event: DomainEvent) { switch (event.type) { case 'OrderCreated': this.orders.set(event.aggregateId, { id: event.aggregateId, status: 'NEW', total: 0 }); break; case 'ItemAdded': const o = this.orders.get(event.aggregateId); if (o) o.total += event.payload.price; break; case 'OrderPlaced': this.orders.get(event.aggregateId).status = 'PLACED'; break; } } query() { return Array.from(this.orders.values()); } } ``` ### Snapshot ```typescript class SnapshotStore { async save(agg: any, version: number) { await db.snapshots.put({ aggregateId: agg.id, snapshot: agg.state, version }); } async loadOrReplay(aggId: string, eventStore: EventStore): Promise { const snap = await db.snapshots.get(aggId); const o = new Order(); if (snap) o.state = snap.snapshot; const events = eventStore.load(aggId).filter(e => e.version > (snap?.version ?? 0)); events.forEach(e => o.apply(e)); return o; } } ``` ### Kafka publish (outbox) ```typescript async function placeOrder(orderId: string) { await db.transaction(async tx => { const events = order.place(); await tx.events.append(events); await tx.outbox.insert(events.map(e => ({ topic: 'orders', payload: e }))); }); } // 매 outbox poller → Kafka ``` ### Schema evolution ```typescript function upgradeEvent(e: any) { if (e.type === 'OrderPlacedV1') { return { ...e, type: 'OrderPlaced', payload: { ...e.payload, currency: 'USD' } }; } return e; } ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | Audit-critical | ES + projection | | CRUD simple | Stay CRUD | | Temporal query | ES | | Collaboration | ES + CRDT | | Distributed system | ES + Kafka outbox | | Compliance | ES + immutable store | **기본값**: 매 high-stakes domain = ES + CQRS + outbox + snapshot. 매 simple = CRUD. ## 🔗 Graph - 부모: [[Architecture]] · [[Domain-Driven-Design]] - 변형: [[CQRS]] - 응용: [[Kafka]] - Adjacent: [[Event-Driven-Architecture]] ## 🤖 LLM 활용 **언제**: 매 audit. 매 temporal. 매 collaboration. **언제 X**: 매 simple CRUD. ## ❌ 안티패턴 - **No snapshot**: 매 replay slow. - **No version check**: 매 concurrency conflict. - **Mutate event**: 매 ES violation. - **No upgrade path**: 매 schema break. - **Synchronous projection**: 매 latency. ## 🧪 검증 / 중복 - Verified (Fowler ES, Greg Young, Vernon DDD). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-04-20 | Auto-reinforced | | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — ES + 매 store / aggregate / projection / snapshot / outbox code |