사용자의 변경 의도를 검증하고 aggregate 호출 전 권한과 입력을 확정한다.
명령은 Aggregate를 바꾸고, 조회는 Projection을 읽는다
CQRS의 핵심은 handler 파일을 늘리는 것이 아니라 변경 의도, 도메인 이벤트, 읽기 모델 갱신을 서로 다른 책임 경계로 두는 것이다. 쓰기는 즉시 일관성을 지키고, 조회는 허용된 지연 안에서 따라온다.
명령에서 조회 모델까지의 책임 지도
command -> aggregate -> event -> projection -> query빨간 경로는 상태 변경, 초록 경로는 조회 모델 갱신, 파란 경로는 읽기 요청이다. 가운데 비동기 경계가 consistency lag를 만드는 지점이다.
쓰기 모델의 트랜잭션 안에서 상태 변경과 domain event 생성을 끝낸다.
outbox나 event stream이 projection 재처리와 장애 분리의 기준점이 된다.
조회 최적화 구조로 데이터를 펴되 지연 시간과 재생 가능성을 기록한다.
조회 요청은 aggregate를 우회하고 허용된 consistency lag 안에서 응답한다.
쓰기 모델과 읽기 모델의 경계
responsibility split상태 변경, aggregate 불변식, event 발행을 한 트랜잭션 경계로 묶는다.
화면에 필요한 형태로 projection된 모델을 읽고 부작용 없이 끝낸다.
CommandHandler가 목록 DTO를 조립하거나 QueryHandler가 aggregate method를 호출한다. 수정 방향
event 계약을 기준으로 projection을 만들고, query 응답은 read model schema에 맞춘다.
Consistency lag를 관측하는 방식
eventual consistencyread model이 늦게 보일 수 있음을 SLA로 정하고, eventId, aggregateId, projectedAt을 로그에 남긴다.
projection handler 실패는 command 재실행이 아니라 event replay와 dead-letter 처리로 복구한다.
장애를 어느 경계에서 분리할지 결정한다
same symptom, different boundary권한, 입력, aggregate 불변식 실패는 event를 만들지 않고 즉시 실패 응답으로 끝낸다.
DB 변경과 event 발행 사이의 공백은 outbox 저장 여부와 재발행 로그로 확인한다.
쓰기 성공 뒤 조회가 늦으면 event handler 지연, 큐 적체, read DB 반영 시간을 분리한다.
오래된 read model을 반환할 때는 commandId가 아니라 projectedAt과 model version으로 추적한다.
CQRS는 흐름을 추적할 수 있을 때 의미가 있다.
- CommandHandler 테스트가 aggregate 규칙과 event 생성을 함께 검증하는가?
- Projection은 같은 event를 재생해도 같은 read model을 만드는가?
- QueryHandler 응답에 stale 허용 시간과 model version을 설명할 수 있는가?