stack-analysis
CHAPTER 115 / 120
읽기 약 2분
FUNCTION
CQRS와 이벤트 소싱 기초
핵심 개념
Command/Query 분리·event store·projection·실전 사례 — 고급 패턴.
본문
CQRS — 읽기와 쓰기 분리
[전통적 CRUD]
- 같은 모델로 읽기·쓰기
- 읽기 최적화 vs 쓰기 정합성 충돌
[CQRS]
- Command (쓰기): 트랜잭션·정합성
- Query (읽기): 비정규화·빠름
→ 같은 데이터, 다른 모델간단한 CQRS
// Command — 쓰기
async function placeOrder(input: PlaceOrderCommand) {
return db.$transaction(async (tx) => {
const order = await tx.order.create({ data: input });
await tx.orderItem.createMany({ data: input.items });
return order;
});
}
// Query — 읽기 (Materialized View)
async function getOrderListView() {
return db.$queryRaw`
SELECT
o.id,
o.created_at,
u.name as customer_name,
COUNT(oi.id) as item_count,
o.total,
o.status
FROM orders o
JOIN users u ON u.id = o.user_id
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.id, u.name
ORDER BY o.created_at DESC
`;
}
// 또는 별도 read DB
async function getOrderListView() {
return readDb.orderListView.findMany({
orderBy: { createdAt: 'desc' },
});
}Event Sourcing
[일반 DB]
- 현재 상태만 저장
- 변경 이력 별도 audit log
[Event Sourcing]
- 모든 변경을 이벤트로 저장
- 현재 상태 = 모든 이벤트 재생
예: 계좌
[일반]
balance: 50000
[Event Sourcing]
2026-01-01: AccountOpened (initial 0)
2026-01-15: Deposited (10000)
2026-01-20: Deposited (50000)
2026-02-01: Withdrew (10000)
balance = 50000 (재계산)Event Store
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
aggregate_id UUID NOT NULL, -- 어떤 엔티티
aggregate_type VARCHAR NOT NULL, -- 'Account', 'Order'
event_type VARCHAR NOT NULL, -- 'Deposited', 'Withdrew'
event_data JSONB NOT NULL,
event_version INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (aggregate_id, event_version) -- 동시성 제어
);
CREATE INDEX ON events(aggregate_id, event_version);
CREATE INDEX ON events(aggregate_type, created_at);Aggregate (도메인 객체)
class Account {
private state = {
id: '',
balance: 0,
version: 0,
};
// 이벤트로부터 상태 복원
static replay(events: Event[]): Account {
const account = new Account();
for (const event of events) {
account.apply(event);
}
return account;
}
apply(event: Event) {
switch (event.type) {
case 'AccountOpened':
this.state.id = event.data.id;
this.state.balance = 0;
break;
case 'Deposited':
this.state.balance += event.data.amount;
break;
case 'Withdrew':
if (this.state.balance < event.data.amount) {
throw new Error('Insufficient funds');
}
this.state.balance -= event.data.amount;
break;
}
this.state.version = event.version;
}
// Command → 새 이벤트 생성
deposit(amount: number): Event[] {
return [{
type: 'Deposited',
data: { amount, at: new Date() },
version: this.state.version + 1,
}];
}
}
// 사용
async function deposit(accountId: string, amount: number) {
const events = await eventStore.load(accountId);
const account = Account.replay(events);
const newEvents = account.deposit(amount);
await eventStore.append(accountId, newEvents);
}Projection (Read Model)
// 이벤트 → 읽기 모델 자동 갱신
async function projectAccount(event: Event) {
switch (event.type) {
case 'AccountOpened':
await readDb.accountSummary.create({
data: { id: event.aggregateId, balance: 0 },
});
break;
case 'Deposited':
await readDb.accountSummary.update({
where: { id: event.aggregateId },
data: { balance: { increment: event.data.amount } },
});
break;
case 'Withdrew':
await readDb.accountSummary.update({
where: { id: event.aggregateId },
data: { balance: { decrement: event.data.amount } },
});
break;
}
}
// Kafka consumer
consumer.run({
eachMessage: async ({ message }) => {
const event = JSON.parse(message.value!.toString());
await projectAccount(event);
},
});장단점
✅ 장점:
- 완전한 audit log (자동)
- 시점 복구 (특정 날짜로 재생)
- 여러 read model (다른 관점)
- 이벤트 분석 (BI)
❌ 단점:
- 학습 곡선 가파름
- Snapshot 필요 (큰 aggregate)
- 이벤트 스키마 변경 어려움
- 디버깅 어려움
→ 금융·감사 필수 도메인에 적합
→ 단순 CRUD에는 과함적합 도메인
[적합]
- 금융 (계좌·거래)
- 이커머스 (주문 흐름)
- 의료 (진료 기록)
- 법무 (계약·이력)
[부적합]
- 단순 콘텐츠 (블로그)
- 이미 존재하는 데이터의 캐시
- 트래픽 적은 내부 도구다음 챕터
CH.116 "서버리스: Vercel Edge + AWS Lambda".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 코드의 CQRS Event Sourcing 부분을 분석해서 실전 분석 + 개선 우선순위를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
CQRS Event Sourcing 관련 베스트 프랙티스 5가지를 비교 분석해서 패턴 추출를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 CQRS Event Sourcing 최적화 가능 위치를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 시장의 CQRS Event Sourcing 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
CQRS와 이벤트 소싱 기초는 이 3가지만 확실히 잡으세요
1.CQRS = Command(쓰기 정합성) + Query(읽기 빠름) 분리
2.Event Sourcing = 모든 변경을 이벤트로 — 재생 가능
3.학습 곡선 가파름 — 금융·감사 필수 도메인에만
공유하기
진행도 115 / 120