도메인 주도 설계를 조금 공부해 봤다면 들어봤을만한 단어, CQRS. 단어까지 들어봤다면 정의는 Command Query Responsibility Segregation이라는 것인지도 알고 무엇인지는 개념적으로 알 것 같은데, 실전에서 어떻게 쓰이고 있을까요? 

Command는 Entity의 State를 변경하기 위해 요청을 받는 것을 뜻하고, Query는 요청에 대한 응답을 해주는데 왜 책임을 분리하여 사용하는지 간단한 예제를 통해 알아보겠습니다.

많이 보았을만한 엔티티간 관계입니다.

사용자, 수업, 수강 엔티티

Command는 어느 엔티티의 상태 변화를 책임지는 것이라서 어렵지 않은 내용이지만, Query라는 것이 사실은 저도 처음에는 조금 헷갈렸습니다. 성능을 위해 비정규화를 해야한다는 뜻인지, 아니면 데이터베이스를 Replica를 통해 ReadOnly 데이터베이스를 만들어야 한다는 뜻인지 구분이 되지 않았거든요. 사실 결국에는 Query라는 것은 단 하나만 의미하는 것이 아니라 모든 것을 내포하고 있다는 것입니다.

자, 일단 관리자 입장에서 아래와 같은 리포틑 보고 싶다면 어떻게 해야 할까요?

코스명 사용자명 수강 등록일
수학 홍길동 2022-01-31
과학 홍길동 2022-01-10
영어 홍길동 2022-01-30

당연히 데이터 베이스에서 조인하여 결과를 만들어내야 하는데, 우리 도메인 모델에는 이를 수용할 수 있는 모델이 존재하지 않습니다. 현재 도메인 모델에 맞게 작업을 하려면, 코스, 사용자, 수강신청 정보 각각을 DB에서 읽은 후 시스템 내에서 JOIN을 해야하는 것이지요. 당연히 성능이 떨어질 수 밖에 없습니다. 그러므로, 리포트를 보여주기 위한 Report용 모델을 추가하여 데이터 베이스에서 바로 JOIN하여 데이터를 가져오게 하는 것이지요. 1단계 Query의 분리를 뜻합니다.

Context.Enrollments.Select(s => new ReportModel
{
    CourseName = s.Course.Name,
    UserName = s.User.Name,
    DateEnrolled = s.DateEnrolled
})

2단계는 Monolithic 아키텍처에서 일반 데이터베이스와 ReadOnly 데이터 베이스를 구축하고 Replica를 이용하여 데이터 복사를 통해 ReadOnly에 반영하고 ReadOnly 데이터베이스는 Query에 최적화 한다는 방법인데, Monolithic 구조에서 굳이 시스템을 이렇게 복잡하게 만들 필요가 전~~혀 없습니다. 배보다 배꼽이 큽니다. 제가 아키텍트라면 절대 수용하지 않을 구조입니다.

3단계는 조금 더 고차원으로 마이크로 서비스가 등장합니다. 사용자, 수업 등의 마이크로 서비스가 존재하고 추가적으로Report Service라는 마이크로 서비스가 존재하고 사용자, 수업, 수강에서 발생하는 모든 이벤트를 받아들여 비정규화된 테이블을 가지고 JOIN 없이 최적의 질의 결과를 뽑아내게 할 수도 있습니다. 성능적으로는 최상일 것 같은나, 단 한가지의 단점인 Eventually Consistency 문제가 발생할 수 있다는 것이지요. 내가 수강 등록을 했는데, 바로 내가 등록한 수강정보가 보이지 않는다? 사용자 입장에서는 어리둥절한 현상이지요. 왜냐하면, 일반 사용자는 모든 데이터는 동기화되어 진행된다고 생각을 하거든요.

사실 저도 이런 황당한 경우를 몇번 본적이 있습니다. Wayfair라는 쇼핑몰에서 상품을 주문했는데, 주문 내역에 보이지를 않는 것이죠. 약 3~5분간 보이지를 않다가 이메일을 받고 들어가보니, 그제서야 주문 내역을 볼 수 있었다는... 처음 몇분간은 이 사이트가 사기인가 라는 것까지 의심을 하게 되더라는...

아무튼, CQRS에서 Query 부분이 이해가 안되었거데 헷갈렸던 개발자에게 도움이 되었으면 합니다.

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기