무엇인가
뿌리는 Bertrand Meyer 의 CQS(Command-Query Separation) 다. Object-Oriented Software Construction(1988)에서 Meyer 는 메서드 수준의 규율을 제시했다 — 하나의 메서드는 상태를 바꾸거나(command) 값을 돌려주거나(query) 둘 중 하나만 해야 하며, "질문이 답을 바꿔서는 안 된다". Greg Young 은 2000년대 후반 이 규율을 메서드가 아니라 시스템 수준으로 끌어올려 CQRS(Command Query Responsibility Segregation)라 이름 붙였다. 쓰기와 읽기를 아예 별개의 모델로 분리하자는 것이다.
| 용어 | 뜻 | 비고 |
|---|---|---|
| 쓰기 모델 | 명령(command)을 받아 검증하고 상태를 바꾸는 단 하나의 모델 | 불변식(invariant)이 사는 곳 — 진실의 원천 |
| 읽기 모델 | 특정 화면·질의에 맞게 접어 둔 데이터 표현 | 화면 수만큼 여럿이어도 된다 |
| 투영(projection) | 쓰기 쪽의 변경을 읽기 모델로 옮겨 적는 변환 | 이벤트를 구독해 갱신하는 경우가 전형 |
| 최종 일관성 | 읽기 모델이 쓰기 모델을 잠시 늦게 따라잡는 것을 허용 | 분리의 대가이자, 분리를 가치 있게 하는 조건 |
왜 분리하는가. 쓰기는 정합성과 불변식이 중요하고, 읽기는 화면마다 다른 모양·다른 성능이 중요하다. 하나의 정규화된 모델로 둘을 다 시키면 양쪽 다 어정쩡해진다. 읽기 모델을 여럿 두면 새 화면 = 새 투영 하나 추가가 되고, 쓰기 모델은 건드리지 않는다.
Young 자신이 반복해서 단서를 달았다. 첫째, CQRS 는 이벤트 소싱이 아니다 — 둘은 궁합이 좋아 자주 함께 쓰일 뿐 별개 패턴이다(이벤트 소싱은 별도 항목). 둘째, CQRS 는 시스템 전체에 씌우는 최상위 아키텍처가 아니라 필요한 부분(bounded context)에만 국소 적용하는 패턴이다. Martin Fowler 도 bliki 의 CQRS 항목에서 "대부분의 시스템엔 과하다"고 같은 경고를 남겼다.
우리 모델과의 접점
개념 모델 5장과 원칙 1·7은 CQRS 의 어휘로 거의 그대로 번역된다. 지도의 접점 문구 — "단일 소스, 렌즈는 필터+투영"의 엔지니어링 대응물 — 를 표로 펴면:
| CQRS | 우리 모델 | 비고 |
|---|---|---|
| 쓰기 모델 하나 | 사건(Event) — 단일 소스(원칙 1) | "렌즈는 문(門)이지 창고가 아니다" |
| 읽기 모델 여럿 | 렌즈 = 필터+투영 — 여정·수첩·여비 + 2차 렌즈 | "렌즈는 셋이 정원이 아니라 축의 수만큼 열리는 문" |
| 화면별 맞춤 투영 | 세 선택 속성이 렌즈 노출을 결정 — 날짜→여정 · 상태→수첩 · 배정→여비 | 투영마다 자기 필터 조건이 있다 |
| 투영은 파생물 | 시제·롤업·순자산 곡선·미배정 자금 — 저장보다 파생(원칙 7) | 단, 물질화 여부는 어긋남 — 아래 참조 |
| 불변식은 쓰기 쪽에 | "일회성 이동은 반드시 사건에 앵커된다"(§6) 같은 제약 | 렌즈가 아니라 데이터 규칙이 삼위일체를 보장 |
어긋남이 두 군데 있고, 둘 다 의도적이다.
- 우리는 '분리'는 버리고 '투영'만 취했다. CQRS 의 R 은 책임 분리(segregation)인데, 우리 렌즈는 읽기 전용이 아니다 — "마스터 관리 화면은 따로 없고 렌즈가 곧 관리 화면"이며, 어디서 입력하든 같은 객체를 편집하고 즉시 상호 반영된다. 최종 일관성이라는 대가도 치르지 않는다. 개인 기록 도구의 데이터 규모에서는 분리의 비용이 이득을 넘어서기 때문이고, 이는 "필요한 곳에만 국소 적용하라"는 Young 의 단서를 따른 셈이다.
- 읽기 모델을 물질화하지 않는다. 전형적 CQRS 는 투영을 별도 저장소(비정규화 테이블·머티리얼라이즈드 뷰)에 실제로 써 둔다. 우리는 원칙 7에 따라 계산으로 될 것은 저장하지 않는다 — 시제도, 곡선도, 렌즈의 목록도 조회 시점에 파생한다. 투영이라는 개념은 같되, 그것을 데이터로 두느냐 계산으로 두느냐가 다르다.
Event[] 를 props 로 받는 투영 컴포넌트로 내려간다. 그러면 여정·수첩·여비·프로필이 전부 store.emit() 을 구독하는 동렬의 읽기 모델이 된다 — 원칙 1을 문서가 아니라 코드 구조에 새기는 리팩터.생각할 거리
- 편집하는 투영의 명령 어휘. CQRS 라면 렌즈는 읽기만 하고 변경은 명령으로 보내야 한다. 우리는 렌즈에서 직접 편집하지만, D1 이후엔 결국 각 렌즈의 편집이 store 로 보내는 명령의 집합(추가·이동·삭제·승격…)으로 정리된다. 이 명령 목록을 명시적으로 닫아 두는 것과 자유 변형을 허용하는 것 — "일회 Flow 는 사건에 앵커"(§6) 같은 불변식을 어느 층이 지키는가가 여기서 갈린다.
- 파생의 물질화는 언제 정당한가. 원칙 7은 저장보다 파생이지만, CQRS 가 투영을 저장하는 이유는 조회 시점 계산이 비싸지기 때문이다. 수십 년치 사건 위에서 시나리오 N+1벌의 곡선(B6 "모두 비교")을 매번 재계산하는 날이 오면, 캐시된 투영(
store.emit()으로 무효화)은 원칙 7 위반인가, 아니면 원칙이 금지한 것은 '저장'이 아니라 '정본화'일 뿐인가. - 최종 일관성은 이미 와 있다. 한 기기 안에서는 즉시 반영이지만, 핸들+PIN 계정의 대리 기록처럼 두 기기가 같은 기록을 만지는 순간 다른 기기의 렌즈는 최종 일관적인 읽기 모델이 된다. 늦게 도착한 투영을 어떻게 보여 줄 것인가 — Local-first 항목의 동기화 질문과 같은 자리에서 만난다.
- 겹(시나리오)은 투영의 매개변수인가, 둘째 쓰기 모델인가. 원칙 5의 겹은 "같은 데이터를 다른 세계로 접는" 투영 매개변수처럼 굴지만, proto 의 B1·B2 구현은 copy-on-write 사본·removed 마커라는 쓰기를 낳았다. 겹이 읽기 쪽 개념(투영 파라미터)으로 남는지, 슬며시 두 번째 쓰기 모델이 되고 있는지 — 델타만 승격한다는 7장의 순환과 함께 검토할 것.
더 찾아보기
- Greg Young, CQRS Documents(2010년경 배포된 정리 문서) — 검색:
Greg Young CQRS Documents pdf. 강연 녹화도 여럿:Greg Young CQRS talk. - Martin Fowler, bliki "CQRS" — martinfowler.com/bliki/CQRS.html — 패턴의 요약과 "대부분엔 과하다"는 경고를 함께 담은 짧은 표준 참조.
- Bertrand Meyer, Object-Oriented Software Construction, 1988 — CQS 의 원전. 검색:
command query separation Meyer. - 검색:
CQRS read model projection·azure architecture cqrs pattern— 클라우드 벤더 문서에 도해가 잘 정리돼 있다. - 같은 장의 이웃: 파셋 분류(여러 축의 투영이라는 같은 발상의 도서관학판), Zettelkasten(구조를 미리 정하지 않는 축적). 쓰기 쪽 원장의 계보는 이벤트 소싱 / 불변 로그, 회계판 "상태는 파생"은 플레인텍스트 회계.
내 생각
이 메모는 지도 페이지의 같은 항목과 공유되며, 이 기기(브라우저)에 저장됩니다.