단일 모델의 단점

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

  • 조회화면 특성상 빠를수록 좋은데 여러 애그리것에서 데이터를 가져와야 할 경우 구현 방법을 고민해봐야 한다.
  • 3장에서 언급한 ID를 이용해서 애그리것을 참조하는 방식을 사용하면 즉시로딩 방식과 같은 JPA의 쿼리 관련 최적화 기능을 사용할 수 없다.
  • 이는 한번의 select 쿼리로 조회화면에 필요한 데이터를 읽어올 수 없어 조회속도에 문제가 생길 수 있다.
  • 애그리것간의 연관을 ID가 아니라 직접 참조하는 방식으로 연결해도 고민거리가 생긴다.
  • 조회 화면의 특성에 따라 같은 연관도 즉시 로딩이나 지연 로딩으로 처리해야 하기 때문이다.
  • 경우에 따라 DBMS가 제공하는 전용기능을 이용해 조회 쿼리를 작성해야 해서 JPA의 네이티브 쿼리를 사용해야 할 수도 있다.

  • 이런 고민이 발생하는 이유는 시스템의 상태를 변경할 때와 조회할 때 단일 도메인 모델을 사용하기 떄문이다.
  • 객체 지향으로 도메인 모델을 구현할 떄 주로 사용하는 ORM 기법은 Order.cancel()이나 Order.changeShippingInfo() 처럼 도메인의 상태를 변경하는데 적합하지만 주문 상세 조회화면 처럼 여러 애그리것에서 데이터를 가져와 출력하는 기능을 구현하기에는 고려할 것들이 많아서 구현을 복잡하게 만드는 원인이 된다.
  • 이런 구현 복잡도를 낮추는 간단한 방법이 있는데 그것은 바로 상태변경을 위한 모델과 조회를 위한 모델을 분리하는 것이다.

CQRS

  • 시스템이 제공하는 기능은 크게 두가지로 나누어 생각할수 있다.
  • 하나는 상태를 변경하는 것이고 하나는 상태 정보를 조회하는 것이다.
  • 도메인 모델 관점에서 상태 변경은 주로 한 애그리것의 상태를 변경한다.
  • 예를 들어 주문 취소 기능과 배송지 정보 변경 기능은 한 개의 Order 애그리것을 변경한다.
  • 반면에 조회 기능은 한 애그리것의 데이터를 조회할 수도 있지만 두개이상의 애그리것에서 데이터를 조회할수도 있다. ( 앞에서 얘기함 주문 상세정보조회 )

  • 단일 모델로 두 종류 ( 상태 변경, 상태 조회 ) 의 기능을 구현하면 모델이 불필요하게 복잡해진다.
  • 이럴때 CQRS를 사용하여 복잡도를 해결한다.

  • CQRS는 상태를 변경하는 명령 모델과 상태를 제공하는 조회 모델을 분리하는 패턴이다.
  • CQRS는 복잡한 도메인에 적합하다.
  • 도메인이 복잡할수록 명령기능과 조회 기능이 다루는 데이터 범위에 차이가 발생하는데 이 두기능을 단일 모델로 처리하면 조회 기능의 로딩속도를 위해 모델 구현이 필요이상으로 복잡해지는 문제가 발생한다.
  • 예를 들어 온라인 쇼핑에서 다양한 차원에서 주문/판매 통계를 조회해야 한다고 해보자.
  • JPA기반의 단일 도메인 모델을 사용하면 통계값을 빠르게 조회하기 위해 JPA와 관련된 다양한 성능관련 기능을 모델에 적용해야 한다.
  • 이런 도메인에 CQRS를 적용하면 통계를 위한 조회 모델을 별도로 만들기 때문에 조회 떄문에 도메인이 복잡해지는 것을 막을 수 있다.

  • CQRS를 사용하면 각 모델에 맞는 구현 기술을 선택할 수 있다.
  • 예를 들어, 명령 모델은 객체 지향에 기반해서 도메인 모델을 구현하기에 적당한 JPA를 사용해서 구현하고, 조회 모델은 DB 테이블에서 SQL로 데이터를 조회할 떄 좋은 MyBatis를 사용해서 구현할수 있다.

출처: https://minkukjo.github.io/dev/2020/11/24/DDD-11/

  • 위의 그림을 보면 조회 모델에는 응용 서비스가 존재하지 않는다.
  • 단순 데이터를 읽어와 조회하는 기능은 로직이 복잡하지 않기 떄문에 컨트롤러에서 바로 DAO를 실행해도 무방하다.
  • 물론 데이터를 표현 영역에 전달하는 과정에서 몇가지 로직이 필요하다면 응용 서비스를 두고 로직을 구현하면 된다.

출처: https://minkukjo.github.io/dev/2020/11/24/DDD-11/

  • 위 그림은 명령 모델과 조회 모델의 설계를 보여주고 있다.
  • 상태 변경을 위한 명령 모델은 객체를 기반으로 한 도메인 모델을 이용해서 구현한다.
  • 반면에 조회 모델은 주문 요약 목록을 제공할때 필요한 정보를 담고 있는 데이터 타입을 이용한다.
  • 두 모델 모두 주문과 관련되어 있지만 명령 모델은 상태를 변경하는 도메인 로직을 수행하는데 초점을 맞춰 설계했고, 조회 모델은 화면에 보여줄 데이터를 조회하는데 초점을 맞춰 설계 했다

  • 명령모델과 조회모델이 같은 구현기술을 사용할 수도 있다.
  • 이 내용은 5장의 조회 전용 기능에서 설명한다.
  • 명령모델과 조회모델이 다른 데이터 저장소를 사용할 수도 있다.
  • 명령 모델은 트랜잭션을 지원하는 RDBMS를 사용하고 조회 모델은 조회 성능이 좋은 메모리 기반 NoSQL을 사용할 수 있을것이다.

출처: https://minkukjo.github.io/dev/2020/11/24/DDD-11/

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

  • 명령 모델과 조회 모델이 서로 다른 데이터 저장소를 사용할 경우 데이터 동기화 시점에 따라 구현방식이 달라질 수 있다.
  • 명령 모델에서 데이터가 바뀌자마자 변경 내역을 바로 조회 모델에 반영해야 한다면 동기 이벤트와 글로벌 트랜잭션을 사용해서 실시간으로 동기화를 할 수 있다.
  • 하지만 10장에서 언급한것처럼 글로벌 트랜잭션은 전반적인 성능이 떨어지는 단점이 있다.

  • 서로 다른 저장소의 데이터를 특정 시간 안에만 동기화 하면 된다면 비동기로 데이터를 전송해도 된다.
  • 예를 들어 통계 처리 목적으로 조회 전용 저장소를 구축했다고 하자, 통계 데이터는 수초, 수분 또는 1시간 단위로 최근 데이터를 반영해도 문제가 되지 않을 때가 많다.
  • 이러한 경우라면 비동기로 데이터를 보냄으로써 데이터 동기화로 인해 명령 모델의 성능이 나빠지지 않도록 할 수 있다.

웹과 CQRS

  • 일반적으로 웹서비스는 상태를 변경하는 요청보다 상태를 조회하는 요청이 많다.
  • 포털이나 대형 온라인 쇼핑몰과 같이 조회 기능 요청 비율이 월등히 높은 서비스를 만드는 개발팀은 조회 성능을 높이기 위해 다양한 기법을 사용한다.
  • 기본적으로 쿼리 최적화를 해서 쿼리 실행 속도 자체를 높이고 메모리에 조회 데이터를 캐시해서 응답 속도를 높이기도 한다. 조회 전용 저장소를 따로 사용하기도 한다.

  • 이렇게 조회 성능을 높이기 위해 다양한 기법을 사용하는 것은 결과적으로 CQRS를 적용하는 것과 같은 효과를 만든다.
  • 메모리에 캐시하는 데이터는 DB에 보관된 데이터를 그대로 저장하기 보다는 화면에 맞는 모양으로 변환한 데이터를 캐시할 때 성능이 더 유리하다. 즉 조회전용 모델을 캐시하는 것이다.
  • 비슷하게 조회 속도를 높이기 위해 쿼리를 최적화 한다는 것은 조회 화면에 보여질 데이터를 빠르게 읽어올 수 있도록 쿼리를 작성한다는 것이다.

  • 대규모 트래픽이 발생하는 웹 서비스는 알게 모르게 CQRS를 적용하게 된다.
  • 단지 명시적으로 명령모델과 조회모델을 구분하지 않을 뿐이다.
  • 조회 속도를 높이기위해 별도 처리를 하고 있다면 명시적으로 명령모델과 조회모델을 구분하자.
  • 이를 통해 조회 기능 떄문에 명령이 복잡해지는 것을 방지하고 명령 모델에 관계없이 조회 기능에 특화된 구현 기법을 보다 쉽게 적용할 수 있다.

CQRS 장단점

장점

  • CQRS 패턴을 적용할떄 얻을 수 있는 장점중 하나는 명령 모델을 구현할 떄 도메인 자체에 집중할수 있다는 것이다.
  • 복잡한 도메인은 주로 상태 변경 로직이 복잡한데 명령모델과 조회모델을 구분하면 조회성능을 위한 코드가 명령모델에 없으므로 도메인 로직을 구현하는데 집중할 수 있다.
  • 또한 명령 모델에서 조회 관련 로직이 사라져 복잡도를 낮춰준다.

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

단점

  • 첫번째 단점은 구현해야 될 코드가 더 많다는 점이다.
  • 단일 모델을 사용할떄 발생하는 복잡함때문에 발생하는 구현 비용과 조회 전용 모델을 만들떄 발생하는 구현 비용을 따져봐야 한다.
  • 도메인이 복잡하거나 대규모 트래픽이 발생하는 서비스라면 조회 전용 모델을 만드는것이 향후 유지보수에 유리할 수 있다.
  • 반면에 도메인이 단순하거나 트래픽이 많지않은 서비스라면 조회 전용 모델을 만들 이유가 없다.

  • 두번째 단점은 명령모델과 조회모델을 다른구현 기술을 사용해서 구현하기도하고 경우에 따라 다른저장소를 사용하기도 하고 데이터 동기화를 위해 메시징 시스템을 도입해야 할수도 있다.
  • 그렇기 떄문에 더 많은 구현기술이 필요하다는것이 단점이다.

  • 이런부분들을 잘 트레이드 오프하여 CQRS를 도입할지 결정해야 한다.
  • 도메인이 복잡하지 않은데 CQRS를 도입하면 두 모델을 유지하는 비용만 높아지고 얻을 수 있는 이점은 없다.
  • 반면에 트래픽이 높은 서비스인데 단일모델을 고집하면 유지보수 비용이 오히려 높아질 수 있으므로 CQRS 도입을 고려해보자.

+ Recent posts