CQRS: 명령과 조회의 분리

xellos·2022년 5월 22일
1

DDD

목록 보기
1/1

단일 모델의 단점

주문 내역 조회 기능을 구현하려면 여러 애그리거트에서 데이터를 가져와야 한다. Order에서 주문정보를 가져와야 하고, Product에서 상품 이름을 가져와야 하고, Member에서 회원 이름과 ID를 가져와야 한다.

조회화면 특성 조회 속도가 빠를 수록 좋은데 여러 애그리거트의 데이터가 필요하면 구현 방법을 고민해야 한다. JPA 의 경우 여러 조인이 엉켜있는 경우, fetch join 등의 최적화 기능을 사용하기 어렵다. 이는 한 번의 SELECT 쿼리로 조회 화면에 필요한 데이터를 읽어올 수 없어 조회 성능에 문제가 생길수 있음을 암시한다.

또한, DBMS가 제공하는 전용 기능에 의존하고 있다면 JPA 가 아닌 네이티브 쿼리를 사용해야할 수도 있다.

이러한 고민이 발생하는 이유는 시스템 상태를 변경할 때와 조회할 때 단일 도메인 모델을 사용 하기 때문이다.
객체 지향으로 도메인 모델을 구현할 때 주로 사용하는 ORM 방식은 도메인의 상태를 변경하는데는 적합하지만, 주문 상세화면 조회처럼 여러 애그리거트에서 데이터를 가져와 출력하는 기능을 구현하기에는 고려할게 많아서 구현을 복잡하게 만드는 원인이 된다.

→ 이를 해결하기 위한 간단한 방법이 있는데 바로 상태 변경을 위한 모델조회를 위한 모델을 분리하는 것이다.

CQRS

시스템이 제공하는 기능은 크게 두 가지로 나눌 수 있다.
1. 상태를 변경하는 기능: 새로운 주문 생성, 배송지 정보 변경, 회원 암호 변경 등
2. 상태를 조회하는 기능: 주문 상세 내역 보기, 게시글 목록 보기 등

1) 상태 변경

도메인 모델 관점에서 상태 변경 기능은 주로 한 애그리커트의 상태를 변경한다. 예를들어 주문취소 기능과 배송지 정보 변경 기능은 한 개의 Order 애그리커트를 변경한다.


2) 조회 기능

조회 기능에 필요한 데이터를 표시하려면 두 개 이상의 애그리거트가 필요할 때가 많다. 위에서 언급한 주문 상세내역 조회 기능이 그러하다.


3) 데이터 범위 불일치

상태를 변경하는 범위와 상태를 조회하는 범위가 정확하게 일치하지 않기 때문에 단일 모델로 두 종류의 기능을 구현하면 모델이 불필요하게 복잡해진다. 이때, 단일 모델을 사용할 때 발생하는 복잡도를 해결하기 위해 사용하는 방법이 있는데 바로 CQRS이다.

CQRS는 Command Query Responsibility Segregation 의 약자로 아래와 같이 상태를 변경하는 명령(Command)을 위한 모델과 상태를 데공하는 조회(Query)를 위한 모델을 분리하는 패턴이다.


4) CQRS의 특징


1. 복잡한 도메인에 적합하다: 도메인이 복잡할수록 명령 기능과 조회 기능이 다루는 데이터 범위에 차이가 난다. 이 두 기능을 단일 모델로 처리하면 조회 기능의 로딩 속도를 위해 모델 구현이 필요 이상으로 복잡해진다.
2. CQRS를 사용하면 각 모델에 맞는 구현 기술을 선택할 수 있다: 예를 들어 명령 모델은 객체 지향에 기반해서 도메인 모델을 구현하기에 적당한 JPA를 사용해서 구현하고, 조회 모델은 DB 테이블에서 SQL로 데이터를 조회할 때 좋은 마이바티스를 사용해서 구현하면 된다.

위의 그림을 보면 조회 모델에는 응용 서비스가 존재하지 않는다. 단순히 데이터를 읽어와 조회하는 기능은 응용 로직이 복잡하지 않기 때문에 컨트롤러에서 바로 DAO를 실행해도 무방하다. 로직이 더 필요하면 추후에 응용 서비스 로직을 추가하면 된다.

위의 그림은 명령 모델과 조회 모델 설계의 예시다.

  • 명령 모델: 객체를 기반으로 한 도메인 모델을 이용해서 구현한다.
  • 조회 모델: 주문 요약 목록을 제공할 때 필요한 정보를 담고 있는 데이터 타입을 이용한다.

위의 두 모델은 모두 주문과 관련이 있지만, 명령 모델은 상태를 변경하는 도메인 로직을 구행하는데 초점을 맞춰서 설계했고, 조회 모델은 화면에 보여줄 데이터를 조회하는데 초점을 맞춰 설계했다.

위의 그림과 같이 명령 모델과 조회 모델이 서로 다른 데이터 저장소를 사용할 수도 있다.

  • 명령 모델: 트랜잭션을 지원하는 RDBMS를 사용
  • 조회 모델: 메모리 기반 NoSQL 사용

두 데이터 저장소간 데이터 동기화는 이벤트를 활용해서 처리한다. 명령 모델에서 상태를 변경하면 이에 해당하는 이벤트가 발생하고, 그 이벤트를 조회 모델에 전달해서 변경 내역을 반영하면 된다.

명령 모델과 조회 모델이 버소 다른 데이터 저장소를 사용할 경우 데이터 동기화 시점에 따라 구현 방식이 달라질 수 있다. 명령 모델에서 데이터가 바뀌자마자 변경 내역을 바로 조회모델에 반영해야 한다면 동기 이벤트글로벌 트랜잭션을 사용해서 실시간으로 동기화할 수 있다. 하지만, 이렇게 하면 성능이 떨어진다.


5) 웹과 CQRS

대규모 트래픽이 발생하는 웹 서비스는 알게 모르게 CQRS를 적용하게 된다. 단지 명시적으로 명령 모델과 조회 모델을 구분하지 않을 뿐이다. 조회 속도를 높이기 위해 별도 처리를 하고 있다면 명시적으로 명령 모델과 조회 모델을 구분하자.

이를 통해 조회 기능 때문에 명령 모델이 복잡해지는 것을 막을 수 있고, 명령 모델에 관계없이 조회 기능에 특화된 구현 기법을 보다 쉽게 적용할 수 있다.


6) CQRS 장단점

장점

CQRS 패턴을 적용할 때 얻을 수 있는 장점은 명령모델을 구현할 때 도메인 자체에 집중할 수 있다는 점이다. 복잡한 도메인은 주로 상태 변경 로직이 복잡한데 명령 모델과 조회 모델을 구분하면 조회 성능을 낮추기 위한 코드가 명령 모델에 없으므로 도메인 로직을 구현하는데 집중할 수 있고 그에따라 복잡도가 낮아진다.

또 다른 장점은 조회 성능을 향상시키는데 유리하다는 점이다. 조회 단위로 캐시 기술을 적용할 수 있고, 조회에 특화된 쿼리를 마음대로 사용할 수도 있다. 캐시 뿐만 아니라 조회 전용 저장소를 사용하면 조회 처리량을 대폭 늘릴 수도 있다.
→ 조회 전용 모델을 사용하기 때문에 이를 위한 코드가 명령 모델에 영향을 주지 않는다.

단점

  1. 구현해야 할 코드가 많아진다. 따라서 복잡함 때문에 발생하는 구현 비용과 조회 전용 모델을 만들 때 발생하는 구현 비용을 따져봐야 한다.
  2. 더 많은 구현 기술이 필요하다. 명령 모델과 조회 모델을 다른 구현 기술을 사용해서 구현하기도 하고 경우에 따라 다른 저장소를 사용하기도 한다. 또한 데이터 동기화를 위해 메시징 시스템을 도입해야 할 수도 있다.

0개의 댓글