학습일지/DDD
[DDD] CQRS
Merge Log
2025. 9. 22. 12:53
CQRS
- 명령 (시스템 데이터 변경) 역할을 수행하는 구성요소와
- 쿼리 (시스템 데이터 조회) 역할을 수행하는 구성 요소를
- 나누는 것이 바로 CQRS
변경과 조회 각각 모델을 왜 만드는가?
- 주문 내역 조회 기능을 구현하려면 여러 애그리거트에서 데이터를 가져와야 한다고 가정하자
- Order 에서 주문 정보를 가져오고, Product 에서 상품 이름을 가져오고, Member 에서 회원 이름과 ID 를 가져오는 작업 수행

- 각 애그리거트를 참조하면서 조회하는 방식은 JPA 를 사용한다면 각 참조가 즉시 로딩 혹은 지연 로딩 등의 경우를 생각해봐야 하고 조회 기능에 따라서도 각 참조 도메인의 조회는 달라질 수 있다
- 그러므로 JPA 의 쿼리 관련 최적화 기능을 사용할 수 없다
- 이런 고민이 발생하는 이유는 시스템 상태를 변경하는 모델과 조회에서 사용하는 모델이 같은 모델을 사용하기 때문이다
- 상태 변경을 위한 도메인 모델링은 ORM 과 같은 기술을 통해서 구현하며 적합한 방식이다
- 그러나 주문 상세 조회같은 여러 애그리거트를 조회하여 데이터를 구성하는 경우는 구현을 복잡하게 만든다
- 이도 저도 아닌 잡탕 도메인 모델링이 발생
- 코드 역할/책임 모호
- 의미/가독성 등이 나빠짐
- 유지보수성이 떨어짐
- 더불어 명령과 쿼리는 다루는 데이터가 다르다
- 주문 상세를 보면 회원도 조회하고 상품도 조회하고 주문도 조회하는 등 여러 애그리거트를 수정해야 한다
- 그러나 주문 취소나 주문 변경 같은 경우 주문 애그리거트내에서 수행할 수 있다

- 그리고 명령과 쿼리는 기획된 기능의 주체가 다르다
- 백오피스의 주문 목록 조회 기능
- 사용자의 주문 기능
- 서로 다른 이유로 코드가 변경되며 성능 요구가 기능마다 다르다, 이는 곧 책임의 크기가 적당하지 않다는 의미
- 이런 이유때문에 상태 변경에 대한 모델과 조회에 대한 모델을 분리하는 것 이다
CQRS 의 구현 형태
- CQRS 는 여러 형태로 구현할 수 있다
- 조회 모델을 DB 로 가져오는 시점에서 Join 이나 다른 연산 작업은 제한해야 한다고 권장한다
- 가능하면 DB에 있는 값 그대로 들고와 곧바로 이용 가능한 형태로 제공하는 것을 권장한다
- 조회를 위한 새로운 테이블을 설계해야 함 → 비정규화된 데이터를 사용함 JSON 등 그래서 NoSQL 을 자주 사용함
- 데이터를 분리해서 처리한다는건 서로 다른 기술을 선택할 수 있다는 것을 의미한다 → 조회를 위한 Redis 사용, NoSQL 사용 등
- 크게 명령과 쿼리가 한 프로세스에서 수행되는가 아니면 다른 프로세스에서 수행되는가
- 명령과 쿼리의 모델이 같은 DB 에 존재하는가 다른 DB에 존재하는가

같은 프로세스, 같은 DB 사용

- 코드 수준에서만 명령과 쿼리가 분리
- 데이터 수준에서는 분리하지 않음
- 가장 단순하며 트랜잭션 처리도 쉽다
같은 프로세스, 같은 DB, 다른 테이블

- 명령과 쿼리가 코드 수준에서 분리되며 데이터 수준에서도 분리된다
- 단, 데이터가 같은 DB 에 있는 형태
- 쿼리 전용 테이블을 따로 구성
- 명령이 상태를 변경할때 쿼리 전용 테이블을 함께 변경해야 한다
같은 프로세스 다른 DB

- 상품 목록을 Redis 를 이용해 캐싱
- 쿼리 모델은 Redis 를 사용
- 명령이 데이터를 변경하면 변경 내역을 쿼리 DB에 전달해야 한다
- 다양한 변경 전파 방법이 존재한다
다른 프로세스, 다른 DB

- 명령이 데이터를 변경하면 변경 내역을 쿼리 DB에 전달해야 한다
다른 DB로 구성함으로 인한 변경 전파 방식

- 명령이 직접 쿼리 DB 를 수정하는 방식
- 카프카와 같은 메시징 수단을 이용해서 처리하는 변형도 존재
- 구현이 단순하다
- 데이터 유실 가능성이 존재한다
- 쿼리 DB 나 메시징이 일시적으로 문제가 생기면 쿼리 DB에 반영해야하는 데이터가 유실될 수 있다
- 쿼리 DB 나 메시징의 문제때문에 명령을 수행해야 하는 서비스 자체가 에러를 발생할 수 있다

- 변경 내역을 기록하고
- 별도 전파기를 통해 변경 내역을 전파하는 방식
- 명령은 상태를 변경한 뒤 변경 내역을 별도 테이블에 기록 → 한 트랜잭션으로 처리
- 변경 내역의 유실이 없음
- 대신 전파기를 따로 구현해야 한다
- 중간에 메시징을 두는 변형이 존재

- CDC 기반 처리
- DB 의 변경된 Binary Log 를 읽어서 변경 내역을 확인하고 변경된 데이터를 쿼리에 전달
- 명령 코드에서 변경 내역을 따로 저장하지 않으므로 명령 코드가 단순해진다
- 중간에 메시징을 두는 변형이 존재함
다른 DB 사용시 주의 사항
- 데이터의 유실
- 유실 허용 여부에 따라 DB 트랜잭션 범위가 중요해진다
- 최근 인기글 같은 경우 전파가 일시적으로 안되어도 상관없다
- 허용 가능한 지연 시간
- 지연 시간에 따라 구현의 방식이 변경될 수 있다
- 중복 전달
- 이벤트의 경우 한 메시지가 중복되어 처리되는 문제를 해결해야 한다
- 혹은 메시징을 멱등성있게 설계해야 한다
참고