이벤트 소싱과 CQRS란?
이벤트 소싱(Event Sourcing)과 CQRS(Command Query Responsibility Segregation)는 복잡한 도메인을 다루는 현대 아키텍처의 핵심 패턴입니다. DDD(도메인 주도 설계)와 함께 자주 사용되며, 정보처리기사에서 중요하게 다뤄집니다.
이벤트 소싱
핵심 개념
- 상태 대신 이벤트 저장: 현재 상태를 저장하는 대신 상태 변화를 일으킨 이벤트를 시계열로 저장
- 이벤트 리플레이: 저장된 이벤트를 순서대로 재실행해 현재 상태 재구성
- 불변성(Immutability): 이벤트는 한 번 저장되면 변경 불가. 감사 추적 자동 확보
- Temporal Query: 과거 특정 시점의 상태 조회 가능
이벤트 소싱 구성 요소
- Aggregate: 이벤트를 발행하는 도메인 객체. 일관성 경계. apply 메서드로 이벤트 처리
- Event Store: 이벤트를 영구 저장. EventStoreDB·Kafka·DynamoDB Streams 활용
- Snapshot: 이벤트가 많아질 때 주기적으로 현재 상태 저장. 리플레이 성능 최적화
- Projection: 이벤트로부터 읽기 전용 뷰 생성. 여러 프로젝션으로 다양한 조회 모델
CQRS 패턴
명령-조회 분리
- Command(쓰기 모델): 상태 변경 작업. 결과 반환 없이 사이드 이펙트만 발생. 이벤트 발행
- Query(읽기 모델): 상태 조회 작업. 부수 효과 없음. 최적화된 Read Model 사용
- 분리 이점: 읽기·쓰기를 독립적으로 스케일링. 읽기 모델은 역정규화·캐싱·복수 뷰 가능
이벤트 소싱 + CQRS 통합
- 흐름: Command → Command Handler → Aggregate → Event 발행 → Event Store 저장 → Event Handler → Read Model 업데이트
- 최종 일관성: 쓰기 모델과 읽기 모델은 즉각 동기화되지 않음. 짧은 지연 허용
- Saga/Process Manager: 여러 Aggregate에 걸친 장기 트랜잭션 조율. 보상 트랜잭션(Compensating Transaction)
이벤트 기반 통합
- Outbox 패턴: 이벤트를 DB 트랜잭션과 함께 Outbox 테이블에 저장. 이중 쓰기 문제 해결
- CDC(Change Data Capture): Debezium으로 DB 변경 로그를 이벤트로 변환. Kafka 토픽으로 발행
- Idempotent Consumer: 같은 이벤트 중복 처리 방지. 이벤트 ID로 중복 검사
- Dead Letter Queue: 처리 실패 이벤트를 별도 큐에 저장. 재처리·분석
시험 핵심 포인트
- 이벤트 소싱: 상태 대신 이벤트 시퀀스 저장. 과거 상태 재현 가능
- CQRS: Command(쓰기)와 Query(읽기) 모델 분리. 각각 독립 최적화
- Snapshot: 이벤트 수 증가 시 리플레이 성능 향상을 위한 상태 체크포인트
- Saga: 분산 트랜잭션을 일련의 로컬 트랜잭션과 보상 트랜잭션으로 처리
마무리
이벤트 소싱과 CQRS는 함께 사용될 때 시너지가 크지만, 복잡도도 증가합니다. 감사 추적, 시간 여행 디버깅, 읽기 성능 최적화가 필요한 경우에 적합한 패턴입니다.