본문 바로가기

프로젝트/고민

CQRS패턴 도입 이야기: CQRS 패턴이란?

글을 쓰게 된 계기

앞서 elasticsearch를 적용한 글을 보면 알 수 있듯이, 상품 도메인에 대해서 조회용 저장소, 명령용 저장소가 분리되어 있다. 하지만 코드적으로는 분리되어 있지 않기 때문에 클래스의 책임이 모호한 상태이다. 따라서 CQRS 패턴을 적용함으로써 요구사항이 추가되더라도 확장에 용이하고 클래스가 이전보다 더 확실한 책임을 가질 수 있도록 하고자 한다. 더 나아가 이 이야기의 핵심인 Kafka를 통해 두 저장소 간의 데이터 일관성을 맞추는 과정을 설명할 계획이다.

 

CQRS란 무엇일까?

 

영어 뜻 그대로 CQRS란

  • 명령(시스템의 데이터 변경) 역할을 수행하는 구성 요소와
  • 쿼리(시스템 데이터 조회) 역할을 수행하는 구성 요소를 나누는 것을 의미한다.

 

간단한 예시 코드를 통해 살펴보자

요약하자면 회원 도메인에 대해서 회원을 조회하는 쿼리가 있고, 이메일을 확인하는 명령이 존재한다.

뭐가 좋을까?

  • 명령의 구성요소와 조회의 구성요소를 나누면 오히려 중복되는 코드도 생길 것이고 
  • 개발이 느려지는 느낌도 들 것이다.

따라서 조회와 명령을 나누는 일은 상황에 따라서 비효율적일수도 있다. 하지만 다음의 예시를 보면 일반적으로 CQRS 패턴을 적용하는 것이 더 나을 것이란 생각이 들거다.

 

단일 모델을 사용할 경우

 

위와 같이 요구사항이 점점 추가되고 시스템이 복잡해진다고 생각해보자. 단일 모델을 사용한다면 계속해서 필드와 메소드를 추가하게 될 것이다. 이렇게 작성하다보면 결국 회원 도메인은 로그인 HISTORY 테이블, ORDER 테이블, MEMBER 테이블이 섞이게 된다.

 

따라서 복잡한 시스템을 단일 모델을 사용할 경우

  • 코드 역할/책임 모호해지고
  • 의미/가독성 나빠지며
  • 유지보수성이 떨어게 된다.

JPA를 통해 구현했을 때를 가정해 그림으로 표현하면 다음과 같은 복잡도를 갖게 된다.

 

 

한눈에 봐도 알 수 있듯이 더럽다..


명령 모델과 쿼리 모델을 나누면 문제가 해결될까?

시스템의 복잡도에 따라 모든 문제가 해결되지는 않겠지만 어느 정도는 해결될 것이다.

 

1. 다루는 데이터가 다르다.

 

조회와 명령의 로직을 생각해보자.

 

 


 

CRUD를 많이 해본 사람이라면 명령과 쿼리는 다루는 데이터가 다르다는 것을 금방 알 수 있을 것이다. 

 

일반적으로 명령은 한 영역의 데이터, 쿼리는 여러 영역의 데이터를 다룬다.

 

예를 들어 주문 취소 명령은 order와 orderline만 다룬다

회원 목록 조회, 주문 목록 조회 같은 기능은 주문, 회원, 상품 영역의 데이터를 사용하게 된다.

 

2. 명령과 쿼리는 코드 변경 빈도, 사용자가 다르다.

읽기 모델의 변경

  • 응용 프로그램의 읽기 모델(쿼리)은 고객에게 정보를 제공하는 데 중점을 두고 있다.
  • 따라서 UI의 요구 사항에 따라 빈번하게 변경될 수 있다.

 

쓰기 모델의 변경

  • 쓰기 모델(명령)은 비즈니스 로직에 더 초점을 맞추고 있다.
  • 따라서 비즈니스 규칙이 변경될 때만 변경이 필요하게 된다.

ㅡ> 서로 다른 이유로 코드를 변경하게 된다.

 

읽기 모델의 사용자

  • 대형 쇼핑몰 웹사이트를 예로 들어 보자.
  • 웹사이트 관리자는 새로운 상품을 추가하거나 가격을 변경하는 등의 명령(쓰기) 작업을 주로 수행한다.

쓰기 모델의 사용자

  • 반면에, 고객들은 상품 정보를 보거나 리뷰를 확인하는 등의 쿼리(읽기) 작업을 주로 수행한다.

ㅡ> 사용자 그룹에 맞게 코드를 작성할 필요가 있다.

 

3. 기능마다 성능 요구가 다르다.

다음과 같은 요구 기능이 있다고 가정하자.

  • 사용자의 상품 목록 조회
  • 상품 상세 조회
  • 사용자의 댓글 등록
  • 사용자의 주문
  • 백오피스의 판매 수치

위의 기능중 사용자와 관련된 기능은 무조건 빨라야 한다. 그렇지 않으면 사용자가 다른 서비스로 빠져나갈 가능성이 커질 것이다.

 

반면 '백오피스의 판매수치' 기능은 약간 느리더라도 고객 유치와는 크게 관련이 없을 것이다.

 

따라서 CQRS 패턴을 도입함으로써 앞서 설명한 문제점을 조금이나마 개선해보고자 하는 것이다.

 

마무리

글을 작성하다보니 본문이 길어져 여기서 끊고 다음 글에서는 CQRS를 적용하는 방법에 대해서 설명할 생각이다.

CQRS에 대해서 간단하게 알아봤다. 글을 작성하고 코드를 작성하다보니 다음과 같은 생각을 하게 되었다.

 

CUD(Create Update Delete. Command)는 정규화된 데이터를 가지고 ORM 등을 가지고 아름답게 처리 가능한데,
R(Retrieve. Query)는 점차 요구사항이 증가하면서 복잡하게 처리가 되는 경우가 많이 있다고 생각한다.


이걸 한 모델로 처리하려고 하면 조회 로직 때문에 코드가 복잡하고 지저분하게 되는 것 같다.
CUD는 깨끗한 객체지향 세계에서 보호하고 R은 다양한 Needs에 맞게 그때 그때 만드는 것이 지속 가능한 SW 개발에 도움이 되는 것 같다.

 

즉, 명령 작업은 DDD, OOP 등 객체지향적인 방법을 준수하는 것이 중요하다고 생각하고 쿼리 작업은 DB수준에서의 정말 적절한 조인을 사용하는 것이 중요하다고 생각한다. 어디까지나 현재로써 나의 개인적인 생각이니 앞으로 이러한 고민을 지속했으면 좋겠다.