도메인 주도 개발 11장 (CQRS)

김재연·2025년 6월 8일
post-thumbnail

CQRS

단일 모델의 단점

조회할 때는 생각보다 많은 것들을 고려해야한다.

11장 책에 나온 내용에 따르면, 주문 내역을 조회한다고 하더라도, Product, Member, Order등 총 3개의 애그리거트를 참조해야 하는데, 이를 연관관계를 끊어내기 위해, 간접참조로 진행한다고 하면, 즉시 로딩과 같은 최적화 기능을 사용할 수 없고 이는 성능 문제를 이야기한다.

또한, 직접 참조로 연결하더라도 문제가 생기게 되는데, 조회 화면 특성에 따라 같은 연관도 즉시 로딩이나, 지연 로딩으로 상황에 맞춰 처리해야하기 때문이다. (복잡도를 증가시키는 것 같다.)

또한, DBMS가 제공하는 전용 기능이 필요하면 JPA의 네이티브 쿼리를 사용해야 할 수도 있다. (힌트 같은 것들?)

이런 고민이 발생하는 근본적인 원인은 무엇일까? 바로, 단일 도메인을 사용하기 때문이다. 객체 지향으로 도메인 모델을 구현할 때, 주로 사용하는 ORM기법은 상태 변경 기능을 구현하는 데는 적합하지만, 여러 애그리거트에서 데이터를 가져와 출력하는 기능을 구현하기에는 복잡하다.

이를 해결하기 위해 상태 변경을 위한 모델, 조회를 위한 모델을 분리하는 것이고, 이것이 바로 CQRS이다.

CQRS

시스템이 제공하는 기능은 크게 두 가지로 나뉠 수 있는데, 하나는 상태를 변경하는 기능 하나는 상태 정보를 조회하는 기능이다.

도메인 모델 관점에서 상태 변경 기능은 주로 한 애그리거트의 상태를 변경하게 된다.

하지만, 조회를 진행할 때를 생각해보면 여러 애그리거트가 필요할 때가 많다.

이는 상태를 변경하는 범위와 상태를 조회하는 범위가 정확하게 일치하지 않는 문제로 정의할 수 있고, 단일 모델로 이를 구현하게 되면 당연히 이 문제를 그대로 떠안게 될 것이다.

이를 해결하는 방법이 CQRS(Command Query Responsibility Segregation)이다. 즉, 단순하게 말하면 조회를 위한 모델, 명령을 위한 모델을 분리하는 것이다.

출처 : 도메인 주도 개발 시작하기

만일, 온라인 쇼핑에서 다양한 차원의 주문 판매 통계를 조회해야 한다고 해보자. 그러면 단일 모델에 성능 관련 JPA의 기능을 덕지덕지 붙여야 할 것이고, 점점 단일 모델의 복잡도는 높아질 것이다.

그래서, 이를 CQRS를 적용해서, 통계를 위한 조회 모델을 별도로 만들어, 명령은 JPA로, 조회는 MyBatis를 선택한다는 등의 시도를 진행 해볼 수 있다.

출처 : 도메인 주도 개발 시작하기

잘 보면 응용 서비스가 존재하지 않는다. 조회자체는 굉장히 단순한 편이 많기에, 이렇게 구현할 수 있는데, 만일 복잡하다면 응용 서비스를 두고 도메인 로직을 구현해주면 된다.

출처 : 도메인 주도 개발 시작하기

굉장히 좋은 예이다.

OrderSummary와 같은 것은 조회 모델이기에 모든 데이터가 담겨져있고, 그렇지 않은 상태 변경을 위한 도메인 모델은 우리가 지금까지 설계해오던 애그리거트의 모습을 하고 있다.

OrderSummary와 같은 것들은 네이티브 쿼리를 사용하여 조인해서 한번에 데이터를 가져온다던가 하면 된다.


출처 : 도메인 주도 개발 시작하기

이렇게 CQRS를 통해 각각 DB의 특성에 따라, DB의 종류를 결정해서 사용할 수도, 혹은 리플리케이션을 활용한 경우에서 진가를 발휘하기도 한다.

출처 : 도메인 주도 개발 시작하기

리플리케이션시 서로 다른 DB를 사용하게 되면 이가 굉장히 복잡해질 수 있다. 그렇기 때문에 우리는 이벤트를 사용해서 위와 같이 직접 동기화를 진행해줄 수 있다.

또한, 다음처럼 동기화 시점을 맞춰주기 위해 이벤트를 사용할 경우, 특성에 따라 동기로 진행할지, 혹은 비동기로 진행할지 선택할 수 있다. 동기 이벤트 혹은 글로벌 트랜잭션을 사용하여 처리하여 확실하게 명령, 조회 DB를 동기화 시킬 수 있지만 성능이 떨어진다는 단점이 존재한다.

웹과 CQRS

백엔드 개발자라면 당연히 알 것이다. GET Method가 다른 Http Method보다 훨씬 많이 일어난다는 것을, 그렇기에 Command에 투자하는 리소스, Query에 투자하는 리소스는 분명히 다르게 들어가야한다.

캐싱을 적용하든, 쿼리를 최적화하든, 조회 전용 저장소를 만들거나 별짓을 다한다. 사실 이것들을 적용하게 되면 알게 모르게 CQRS를 적용하는 것과 같은 효과를 만들게 된다.

그러니, CQRS를 적용하는 것과 같은 효과를 만드는 것뿐만 아니라, CQRS를 적용해 이를 조금 더 명시적으로 적용해보자.

CQRS 장단점

CQRS 패턴을 적용할 때 얻을 수 있는 장점은, 명령 모델을 구현할 때 도메인 자체에 집중할 수 있다는 점이다. 아무래도 명령, 조회 기능을 하나의 모델에 구현하게 되면 복잡도가 당연히 향상할 수 밖에 없다. 이를 분리하니 당연히 복잡도는 줄어들 수 밖에 없다.

또한, 이전에 이야기 했던 것과 같이 조회가 훨씬 많이 일어나니 Query 부분에 리소스를 엄청나게 많이 들일 수 있다.

캐시 기술을 적용하거나, 쿼리를 최적화하거나 조회 전용 저장소를 대폭으로 늘릴 수도 있다. 이렇게 적용하더라도, 이는 명령 모델에 전혀 영향을 주지 않는다.

하지만, 구현할 코드양이 많아진다. 그렇기 때문에, 서비스의 특성이 도메인이 복잡하지 않다거나, 대규모 트래픽을 감당할 필요가 없다면 굳이 이렇게 CQRS를 활용할 필요가 없을지도 모른다.

두 번째는 더 많은 구현 기술이 필요하다는 것이다. 동기화를 위해 메시징 시스템을 도입해야 할 수도, 혹은 조회 모델과 명령 모델의 구현 기술을 다른 구현 기술을 사용하기도, 다른 저장소를 사용하기도 한다.

이러한 장단점을 고려해서 도메인이 복잡하지 않거나, 트래픽이 높지 않은데 굳이 CQRS를 도입해서 복잡도를 높이지는 말자.

profile
끊임없이 '성장'하는 개발자 김재연입니다.

2개의 댓글

comment-user-thumbnail
2025년 6월 11일

이거 완전 kpeel5839 네;;

1개의 답글