DDD - CQRS
이 포스트에서는 아래 내용에 대해 알아본다.
- 명령 모델과 조회 모델
- CQRS 의 장단점
매핑되는 테이블은 DDD - ERD 을 참고하세요.
목차
개발 환경
- 언어: java
- Spring Boot ver: 3.2.5
- Spring ver: 6.1.6
- IDE: intelliJ
- SDK: JDK 17
- 의존성 관리툴: Maven
1. 단일 모델의 단점
주문 내역 조회 기능을 구현해야 한다고 해보자.
Order 에서 주문 정보, Product 에서 상품명, Member 에서 회원 정보 등 여러 애그리거트에서 데이터를 가져와야 한다.
조회 화면 특성상 조회 속도가 빠를 수록 좋은데 여러 애그리거트의 데이터가 필요하면 구현 방법을 고민해봐야 한다.
4.2. ID 를 이용한 애그리거트의 간접 참조 에서 본 것처럼 식별자를 이용하여 애그리거트를 참조하는 방식을 사용하면 즉시 로딩 방식과 같은 JPA 의 쿼리 관련 최적화 기능을 사용할 수 없다.
이는 한 번의 SELECT 쿼리로 조회 화면에 필요한 데이터를 읽어올 수 없어 조회 성능에 문제가 생길 수 있다.
4.1. 필드를 이용한 애그리거트 참조 에서 본 것처럼 애그리거트 간 식별자가 아니라 직접 참조하는 방식으로 연결해도 조회 화면 특성에 따라 즉시 로딩이나 지연 로딩으로 처리해야 하는 문제가 생긴다.
이런 고민이 발생하는 이유는 상태를 변경할 때와 조회할 때 단일 도메인 모델을 사용하기 때문이다.
객체 지향으로 도메인 모델을 구현할 때 주로 사용하는 ORM 기법은 도메인 상태 변경 기능을 구현하는 데는 적합하지만 주문 상세 조회 화면처럼 여러 애그리거트에서 데이터를 가져오는 기능을 구현하기에는 고려할 게 많아서 구현을 복잡하게 만드는 원인이 된다.
이런 구현 복잡도를 낮춰주는 방법이 바로 상태 변경을 위한 모델과 조회를 위한 모델을 분리하는 것이다.
2. CQRS (Command Query Responsibility Segregation)
도메인 모델 관점에서 상태 변경 기능은 주로 하나의 애그리거트의 상태를 변경한다.
반면 조회 기능에 필요한 데이터를 표시하려면 여러 개의 애그리거트가 필요할 때가 많다.
상태를 변경하는 범위와 상태를 조회하는 범위가 정확하게 일치하지 않기 때문에 단일 모델로 두 종류의 기능을 구현하면 모델이 불필요하게 복잡해진다.
이렇게 단일 모델을 사용할 때 발생하는 복잡도를 해결하기 위한 방법이 CQRS 이다.
CQRS 는 상태를 변경하는 명령(command) 을 위한 모델과 상태를 제공하는 조회(query) 를 위한 모델을 분리하는 패턴이다.
복잡한 도메인일수록 명령 기능과 조회 기능이 다르는 데이터 범위 차이가 크기 때문에 이 두 기능을 단일 모델로 처리하면 조회 기능의 로딩 속도를 위해 모델 구현이 필요 이상으로 복잡해진다.
예를 들어 통계를 조회하는 기능 구현 시 JPA 기반 단일 도메인 모델을 사용하면 통계값을 빠르게 조회하기 위해 JPA 와 관련된 다양한 성능 관련 기능을 모델에 적용해야 한다.
이런 도메인에 CQRS 를 적용하면 통계를 위한 조회 모델을 별도로 만들어 사용하기 때문에 도메인 모델이 복잡해지는 것을 막을 수 있다.
2.1. 명령 모델과 조회 모델에 다른 구현 기술 적용
CQRS 를 사용하면 각 모델에 맞는 구현 기술을 선택할 수 있다.
명령 모델은 객체 지향에 기반하여 도메인 모델을 구현하기에 적당한 JPA 를 사용하여 구현하고, 조회 모델은 DB 테이블에서 SQL 로 데이터를 조회할 때 좋은 마이바이트를 사용해서 구현할 수 있다.
위 그림을 보면 조회 모델에는 응용 서비스가 없다.
단순히 데이터를 조회하는 기능은 응용 로직이 복잡하지 않기 때문에 컨트롤러에서 바로 DAO 를 실행해도 무방하다.
2.2. 명령 모델과 조회 모델에 같은 구현 기술 적용
명령 모델과 조회 모델이 같은 구현 기술을 사용할 수도 있다.
JPQL 을 이용한 동적 인스턴스 생성과 하이버네이트의 @Subselect
를 사용할 때 동적 인스턴스로 사용할 클래스와 @Subselect
를 적용한 클래스가 조회 모델에 해당한다.
2.3. 명령 모델과 조회 모델 설계
아래는 명령 모델과 조회 모델의 설계 예시이다.
상태 변경을 위한 명령 모델을 객체를 기반으로 한 도메인 모델을 이용하여 구현한다.
반면 조회 모델은 주문 요약 목록을 제공할 때 필요한 정보를 담고 있는 데이터 타입을 이용한다.
두 모델 모두 주문과 관련되어 있지만 명령 모델은 상태를 변경하는 도메인 로직을 수행하는데 초점을 맞춰 설계했고, 조회 모델은 화면에 노출할 데이터를 조회하는데 초점을 맞춰 설계한다.
2.4. 명령 모델과 조회 모델에 다른 데이터 저장소 사용
명령 모델과 조회 모델이 서로 다른 데이터 저장소를 사용할 수도 있다.
명령 모델은 트랜잭션을 지원하는 RDBMS 를 사용하고, 조회 모델은 조회 성능이 좋은 메모리 기반 NoSQL 을 사용할 수 있다.
두 데이터 저장소 간의 데이터 동기화는 이벤트를 활용하여 처리 가능하다.
명령 모델에서 상태를 변경하면 이에 해당하는 이벤트가 발생하고, 그 이벤트를 조회 모델에 전달해서 변경 내역을 반영하면 된다.
명령 모델과 조회 모델 데이터 동기화 시점에 따라 구현 방식이 달라질 수 있다.
- 명령 모델에서 데이터가 변경되자마자 조회 모델에 동기화해야 하는 경우
- 동기 이벤트와 글로벌 트랜잭션을 사용하여 실시간으로 동기화
- 하지만 동기 이벤트와 글로벌 트랜잭션 사용 시 전반적으로 성능이 떨어지는 단점이 있음
- 명령 모델에서 데이터가 변경된 후 특정 시간 안에만 동기화하면 되는 경우
- 비동기로 데이터 전송함으로써 데이터 동기화로 인해 명령 모델의 성능이 나빠지지 않도록 할 수 있음
이벤트에 대한 상세한 내용은
DDD - 이벤트(1): 이벤트, 핸들러, 디스패처,
DDD - 이벤트(2): 비동기 이벤트 처리
를 참고하세요.
2.1. 웹과 CQRS
일반적으로 웹 서비스는 상태 변경 요청보다 상태를 조회하는 경우가 훨씬 많다.
예를 들면 주문 요청보다 상품을 조회 요청이 훨씬 많고, 게시글도 한번 등록한 글을 여러 명이 여러 번 조회한다.
조회 성능을 높이기 위해 아래와 같은 처리를 할 수 있다.
- 기본적으로 쿼리를 최적화하여 쿼리 실행 속도 자체를 높임
- 메모리에 조회 데이터를 캐싱하여 응답 속도를 높임
- 조회 전용 저장소를 따로 사용
이런 기법들은 결과적으로 CQRS 를 적용하는 것과 같은 효과를 만든다.
- 조회 전용 모델을 캐싱
- 메모리에 캐싱하는 데이터는 DB 에 보관된 데이터를 그대로 저장하기 보다 화면에 필요한 데이터를 변환한 데이터를 캐싱할 때 성능에 더 유리
- 조회 속도를 높이기 위해 쿼리를 최적화한다는 것은 조회 화면에 필요한 데이터를 빠르게 읽어올 수 있도록 쿼리를 작성한다는 의미
대규모 트래픽이 발생하는 웹 서비스는 알게 모르게 CQRS 를 적용하고 있지만 명시적으로 명령 모델과 조회 모델을 구분하지 않을 뿐이다.
조회 속도를 높이기 위해 별도 처리를 하고 있다면 명령 모델과 조회 모델을 구분하자.
이를 통해 조회 기능 때문에 명령 모델이 복잡해지는 것을 막을 수 있고, 명령 모델과 관계없이 조회 기능에 특화된 구현 기법으로 보다 쉽게 적용할 수 있다.
2.2. CQRS 장단점
<CQRS 도입 시 장점>
- 명령 모델 구현 시 도메인 자체에 집중 가능
- 복잡한 도메인은 주로 상태 변경 로직이 복잡한데 명령 모델과 조회 모델을 구분함으로써 조회 성능을 위한 코드가 명령 모델에 없으므로 도메인 로직을 구현하는데 집중 가능
- 명령 모델에 조회 관련 로직이 없으므로 복잡도가 낮아짐
- 조회 성능을 향상시키는데 유리
- 조회 단위로 캐시 기술 적용 가능
- 조회에 특화된 쿼리 사용 가능
- 조회 전용 저장소를 사용하여 조회 처리량 향상 가능
<CQRS 도입 시 단점>
- 구현해야 할 코드가 많아짐
- ‘단일 모델을 사용할 때 발생하는 복잡함 때문에 발생하는 구현 비용’ 과 ‘조회 전용 모델을 만들 때 발생하는 구현 비용’ 을 따져봐야 함
- 도메인이 복잡하거나 대규모 트래픽이 발생하는 서비스라면 조회 전용 모델을 만드는 것이 향후 유지 보수에 유리함
- 반면, 도메인이 단순하거나 트래픽이 많지 않은 서비스라면 조회 전용 모델을 따로 만들 때 얻을 이점이 있는지 따져봐야 함
- 더 많은 구현 기술이 필요함
- 명령 모델과 조회 모델을 다른 구현 기술을 사용하여 구현하기도 하고, 경우에 따라 다른 저장소를 사용하기도 함
- 데이터 동기화를 위해 메시징 시스템을 도입해야 할 수도 있음
이런 장단점을 고려하여 CQRS 패턴을 도입할 지 여부를 결정해야 한다.
도메인이 복잡하지 않은데 CQRS 를 도입하면 두 모델을 유지하는 비용만 높아지고 이점은 없다.
반면, 트래픽이 높은 서비스인데 단일 모델로 구현하면 유지 보수 비용이 높아질 수 있으므로 CQRS 도입을 고려하는 것이 좋다.
참고 사이트 & 함께 보면 좋은 사이트
본 포스트는 최범균 저자의 도메인 주도 개발 시작하기을 기반으로 스터디하며 정리한 내용들입니다.