[G1-Sync] Manual knowledge update

This commit is contained in:
Antigravity Agent
2026-05-10 22:08:15 +09:00
parent 21ac3ed255
commit 504fd5fb42
3011 changed files with 380280 additions and 206977 deletions
+175 -104
View File
@@ -2,132 +2,203 @@
id: wiki-2026-0508-event-sourcing-pattern
title: Event Sourcing Pattern
category: 10_Wiki/Topics
status: needs_review
status: verified
canonical_id: self
aliases: [P-REINFORCE-WIKI-EBEBF028]
aliases: [event sourcing, ES, append-only, event store, projection, snapshot]
duplicate_of: none
source_trust_level: A
confidence_score: 0.95
tags: [event-sourcing-pattern, event-driven-architecture-pattern, cqrs-architecture-pattern, domain-driven-design, event-stream-processing, architecture-principles]
verification_status: applied
tags: [architecture, event-sourcing, cqrs, domain-driven-design, audit, kafka]
raw_sources: []
last_reinforced: 2026-05-02
last_reinforced: 2026-05-10
github_commit: pending
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
tech_stack:
language: unspecified
framework: unspecified
language: TypeScript / Java
framework: EventStoreDB / Kafka / Axon
---
# [[Event Sourcing Pattern]]
# Event Sourcing
## 📌 한 줄 통찰 (The Karpathy Summary)
이벤트 소싱 패턴(Event Sourcing Pattern)은 애플리케이션 상태에 대한 모든 변경 사항을 추가 전용 로그(append-only log)에 불변의 이벤트(immutable events) 시퀀스로 캡처하고 저장하는 아키텍처 패턴입니다 [1]. 실시간 데이터를 다루는 애플리케이션에 적합하며, 지속적인 메시지 스트림을 보내 데이터베이스, 웹 서버, 타겟 시스템 등과 통신합니다 [1]. 완전한 감사 추적(audit trails)이 필요하거나 시간적 데이터 분석, 복잡한 비즈니스 로직 처리를 요하는 환경에서 널리 활용됩니다 [1, 2].
## 한 줄
> **"매 state 의 store 의 X — 매 event 의 sequence 의 store"**. 매 current state = 매 replay 의 result. 매 audit + 매 temporal query + 매 CQRS 의 partner. 매 modern: 매 Kafka, EventStoreDB, Axon.
## 📖 구조화된 지식 (Synthesized Content)
* **작동 원리 및 특징:** 이벤트 소싱 패턴은 애플리케이션의 현재 상태를 단순히 저장하는 전통적인 CRUD 모델과 달리, 데이터를 변경하는 모든 동작(트랜잭션)을 삭제나 수정 없이 불변의 이벤트로 추가 전용 로그에 기록합니다 [1, 3]. 시스템은 이러한 일련의 이벤트를 기반으로 데이터의 상태를 재구성합니다 [3].
* **주요 적용 사례:**
* 은행 업무 및 헬스케어와 같이 감사(Audit)가 필수적이고 중요한 시스템 [2].
* "과거 특정 날짜의 계좌 잔액 표시" 등과 같은 시간적 데이터 분석(Temporal data analysis)이 필요한 경우 [2].
* 롤백(Rollbacks)을 포함하여 복잡한 비즈니스 워크플로우를 관리해야 하는 소프트웨어 (예: 주문 처리 시스템) [2].
* 버전 관리를 수행하는 Git, 규정 준수 및 지불 거절 조사를 위해 모든 트랜잭션 데이터를 불변 이벤트로 저장하는 금융 솔루션, 주문 변경의 전체 내역을 추적하는 이커머스 플랫폼 등이 대표적인 실제 사례입니다 [4].
* **CQRS 패턴과의 시너지:** 이벤트 소싱은 명령(쓰기)과 쿼리(읽기) 책임을 분리하는 CQRS(Command Query Responsibility Segregation) 아키텍처 패턴과 함께 구현될 때 매우 강력한 성능 최적화 효과를 제공합니다 [2, 3, 5].
## 매 핵심
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
* **장점 (Pros):**
* **강력한 감사 추적:** "왜 3월 12일에 이 거래가 거절되었는가?"와 같이 애플리케이션 내의 모든 내역에 대한 완벽한 감사 추적 및 문제 원인 파악을 지원합니다 [3].
* **유연성:** 기존 데이터를 마이그레이션할 필요 없이 새로운 프로젝션(projections)을 자유롭게 추가할 수 있는 비즈니스적 유연성을 제공합니다 [3].
* **탁월한 디버깅:** 이벤트를 다시 재생(replay)하여 버그를 완벽히 재현할 수 있는 슈퍼파워를 제공합니다 [3].
* **제약 사항 및 부작용 (Cons & Caveats):**
* **복잡한 버전 및 상태 관리:** 이벤트 구조가 변경될 때 버전을 관리하는 작업이 매우 복잡하며, 수백만 개의 이벤트가 누적된 경우 상태를 빠르게 재구축하기 위해 별도의 스냅샷(snapshots) 메커니즘이 필요합니다 [3].
* **비용 및 성능 이슈:** 이벤트 로그가 증가함에 따라 스토리지 비용이 상승합니다 [3].
* **학습 곡선:** 단순한 CRUD 마인드셋에서 벗어나 이벤트 기반 사고방식(event-based thinking)으로 전환해야 하므로 학습 곡선이 가파릅니다 [3].
* **최종 일관성 수용:** 즉각적인 일관성(immediate consistency)이 필요한 시스템에는 부적합하며, 밀리초 단위의 지연이 발생할 수 있는 최종 일관성(eventual consistency) 구조를 수용해야 합니다 [2, 6].
* **사전 조건:** 팀에 도메인 주도 설계(DDD; Domain-Driven Design)에 대한 전문 지식이 부족하거나 애플리케이션이 단순히 감사 필요성이 없는 CRUD 작업 위주라면 이 패턴의 사용을 피해야 합니다 [2].
### 매 vs CRUD
- **CRUD**: 매 row 의 update.
- **ES**: 매 immutable event 의 append.
- **Projection**: 매 event → 매 read model.
## 🔗 지식 연결 (Graph)
### Related Concepts
### 매 component
- **Event store**: 매 append-only.
- **Aggregate**: 매 command → events.
- **Projection / read model**: 매 query.
- **Snapshot**: 매 replay 최적화.
- **Outbox**: 매 publish reliability.
#### [아키텍처/기반 기술]
* [[Event-Driven Architecture Pattern]]
* 연결 이유: 이벤트 소싱 패턴은 구성 요소들이 비동기적 이벤트를 통해 통신하는 이벤트 기반 아키텍처의 철학을 데이터 저장 및 관리 영역으로 확장한 개념입니다 [1, 7].
* 이 개념을 통해 더 깊게 이해할 수 있는 부분: 이벤트를 생성, 감지, 반응하는 전체 시스템의 비동기적 생태계 원리와 최종 일관성(Eventual Consistency)에 대한 아키텍처적 이해도를 높일 수 있습니다 [6, 7].
* [[CQRS Architecture Pattern]]
* 연결 이유: 이벤트 소싱 패턴은 명령(쓰기)과 쿼리(읽기)를 물리적/논리적으로 분리하는 CQRS 패턴과 시너지를 이루어, 읽기 최적화를 분리하여 수행하는 방식으로 주로 구현됩니다 [2, 3, 5].
* 이 개념을 통해 더 깊게 이해할 수 있는 부분: 누적된 이벤트 로그만으로 빠른 조회가 어려울 때, 별도의 데이터베이스(Read models)를 구성하여 비동기 메시지 브로커를 통해 동기화하고 조회 성능을 극대화하는 방법을 이해할 수 있습니다 [8, 9].
### 매 응용
1. **Banking**: 매 audit.
2. **E-commerce**: 매 order lifecycle.
3. **Compliance**: 매 immutable history.
4. **Collaboration**: 매 Figma.
5. **Game state**: 매 replay.
#### [설계 방법론]
* [[Domain-Driven Design]] (DDD)
* 연결 이유: 소스 데이터는 이벤트 소싱 아키텍처를 도입하기 전에 팀 내에 도메인 주도 설계(DDD) 전문 지식이 갖춰져 있어야 함을 명시하고 있습니다 [2].
* 이 개념을 통해 더 깊게 이해할 수 있는 부분: 복잡한 비즈니스 요구사항과 워크플로우를 어떻게 이벤트 단위로 쪼개고, 어그리게잇(Aggregates)이나 바운디드 컨텍스트(Bounded Contexts) 등의 도메인 모델로 매핑하여 설계할지 이해할 수 있습니다 [2, 10].
### 매 trade-off
- ✅ Audit, replay, temporal query, decouple.
- ❌ Complexity, eventual consistency, schema evolution.
### Deeper Research Questions
* 이벤트 소싱 패턴에서 애플리케이션의 이벤트 구조(Schema)가 변경되었을 때, 하위 호환성을 유지하며 과거의 이벤트를 어떻게 버전 관리(Version handling)하고 해석하는가? [3, 11]
* 상태를 재구축할 때 수백만 개의 이벤트를 모두 재생하는 오버헤드를 막기 위해, 스냅샷(Snapshots)의 생성 주기와 기준은 어떠한 원리로 결정되는가? [3]
* CQRS 패턴과 이벤트 소싱을 결합했을 때 필연적으로 발생하는 읽기 모델과 쓰기 모델 간의 최종 일관성(Eventual consistency) 지연(Delay)을 비즈니스 로직 및 사용자 인터페이스(UI) 측면에서 어떻게 보완할 수 있는가? [6, 9, 12]
* 무한히 증가하는 이벤트 로그로 인해 증가하는 스토리지 비용을 효율적으로 관리하기 위한 아카이빙(Archiving) 전략이나 데이터 관리 방법론은 무엇인가? [3]
* 사용자 데이터의 완전 삭제가 요구되는 규제(예: GDPR의 '잊힐 권리')를 준수해야 할 때, 불변성(Immutability)을 원칙으로 하는 이벤트 소싱 로그에서 개인정보를 어떻게 처리하는가? (소스에 관련 정보가 부족합니다. 외부 조사가 필요합니다.)
## 💻 패턴
### Practical Application Contexts
* **Implementation:** 복잡한 시스템(은행, 헬스케어, 이커머스 등)에서 상태 변경의 이력을 누락 없이 기록하기 위해 데이터베이스의 트랜잭션을 이벤트 스트림 형태로 구현합니다 [2, 4].
* **System Design:** 시스템 설계 시 CQRS 패턴과 짝을 지어, 높은 트래픽에서 읽기 작업과 쓰기 작업의 부하를 분산시키고, 감사(Audit) 기능을 아키텍처 수준에서 강제할 때 적용합니다 [2, 3].
* **Operation / Maintenance:** 운영 중 장애가 발생했을 때 이벤트를 다시 재생하여 프로덕션 환경의 버그를 로컬 테스트 환경에서 정확히 동일하게 재현하고 디버깅하는 강력한 수단으로 활용됩니다 [3].
* **Learning Path:** 백엔드 개발자 및 아키텍트는 단순 CRUD 상태 관리에서 벗어나 이벤트 기반의 사고방식(event-based thinking)과 메시지 기반 시스템, 도메인 주도 설계(DDD)를 우선적으로 학습해야 합니다 [2, 3].
* **My Project Relevance:** 기획 또는 개발 중인 프로젝트가 과거의 상태를 완벽히 추적해야 하거나, 잦은 비즈니스 로직 롤백 처리가 필요한 이커머스 플랫폼, 또는 엄격한 규정 준수가 필요한 금융 서비스라면 핵심 아키텍처로 도입을 고려할 수 있습니다 [2, 4].
### Event store (TypeScript)
```typescript
type DomainEvent = { aggregateId: string; type: string; payload: object; version: number; timestamp: Date };
### Adjacent Topics
* [[Event Stream Processing]]
* 확장 방향: 단순한 이벤트의 저장을 넘어, 발생하는 대량의 일반/주요 이벤트 스트림을 실시간으로 분석하여 비즈니스 이상 징후나 기회를 감지하는 시스템 설계로 확장이 가능합니다 [13, 14].
* [[Message Brokers]] (e.g., Kafka, RabbitMQ)
* 확장 방향: 이벤트 소싱에서 발생한 이벤트를 CQRS 읽기 모델이나 다른 마이크로서비스로 안정적으로 전달하고 동기화하기 위한 메시지 큐 및 브로커 인프라의 활용 기술로 확장할 수 있습니다 [9].
---
*Last updated: 2026-05-02*
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
**언제 이 지식을 쓰는가:**
- *(TODO)*
**언제 쓰면 안 되는가:**
- *(TODO)*
## 🧪 검증 상태 (Validation)
- **정보 상태:** needs_review
- **출처 신뢰도:** A
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
## 🧬 중복 검사 (Duplicate Check)
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
- **처리 방식:** UPDATE (자동 정규화)
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
## 🕓 변경 이력 (Changelog)
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|------|-----------|-----------|--------|
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
## 💻 코드 패턴 (Code Patterns)
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
```text
# TODO
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);
}
}
```
## 🤔 의사결정 기준 (Decision Criteria)
### 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;
}
}
```
**선택 A를 써야 할 때:**
- *(TODO)*
### Projection
```typescript
class OrderListProjection {
private orders = new Map<string, any>();
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()); }
}
```
**선택 B를 써야 할 때:**
- *(TODO)*
### 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<Order> {
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;
}
}
```
**기본값:**
> *(TODO)*
### 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
```
## ❌ 안티패턴 (Anti-Patterns)
### Schema evolution
```typescript
function upgradeEvent(e: any) {
if (e.type === 'OrderPlacedV1') {
return { ...e, type: 'OrderPlaced', payload: { ...e.payload, currency: 'USD' } };
}
return e;
}
```
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
## 매 결정 기준
| 상황 | 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]] · [[Outbox-Pattern]]
- 응용: [[Kafka]] · [[EventStoreDB]] · [[Axon]]
- Adjacent: [[Event-Driven-Architecture]] · [[Saga-Pattern]]
## 🤖 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 |