학습일지/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 트랜잭션 범위가 중요해진다
    • 최근 인기글 같은 경우 전파가 일시적으로 안되어도 상관없다
  • 허용 가능한 지연 시간
    • 지연 시간에 따라 구현의 방식이 변경될 수 있다
  • 중복 전달
    • 이벤트의 경우 한 메시지가 중복되어 처리되는 문제를 해결해야 한다
    • 혹은 메시징을 멱등성있게 설계해야 한다

참고

'학습일지 > DDD' 카테고리의 다른 글

[DDD] 바운디드 컨텍스트  (2) 2025.09.22
[DDD] 도메인 서비스  (0) 2025.09.21
[DDD] 응용 서비스의 역할  (0) 2025.09.21
[DDD] 애그리거트  (2) 2025.09.19
[DDD] 도메인, 엔티티와 밸류, DIP 주의사항  (0) 2025.09.19