brunch

You can make anything
by writing

C.S.Lewis

by 안영회 습작 Sep 30. 2022

CQRS 패턴의 빈 부분과 도메인 모델링

도메인 모델링 세미나 20

동료의 모호한 작명에 대해 의문을 제기하는 대화를 두레이에서 진행했다.

어떻게 피드백을 할까 생각하다가 지난 이틀 사이에 있었던 몇 가지 일들이 떠올라 글로 엮는다.


CQRS 모델의 약점

회사에서 CTO님이 CQRS 모델로 샘플을 만들어본 소감을 듣고 페이스북에 남겼던 메모가 있다. 내가 코드까지 열어볼 여력은 없어서 즉흥적인 생각만 풀어놓은 것이다. 어쩌면 그 메모에서 담지 못한 이야기를 꺼내면 다른 동료의 작업에 대한 피드백에 도움이 될 듯한 생각이 들어 다시 생각을 끄집어낸다.

CTO님이 지목한 CQRS 모델의 약점은 코드 중복 발생 가능성이 높다는 것이었다. 그런데 이는 Command와 Query 사이의 비대칭에 기인한 것이다. 근본적으로 양자는 일대일 대응이 아니다. 그래서, 대응 관계를 잘 풀어나갈 방법을 찾아야 CQRS 패턴 채용이 효과를 발휘할 것이란 생각에 동의하는 것으로 CTO님과 나의 대화는 끝이 났다.


아키텍처와 아키텍처 패턴의 차이

나는 의도적으로 CTO님이 사용한 'CQRS 모델'이라는 표현 대신에 'CQRS 패턴'이라는 말을 사용했다. Vaughn Vernon 표현 인용이기 때문이다. Vaughn Vernon에 따르면 CQRS는 아키텍처 패턴일 뿐 아키텍처일 수 없다.

배경지식이 없다면 말장난처럼 들릴 수 있다. 그래서인지 Vaughn Vernon도 부연을 한다. 아키텍처(혹은 아키텍처 스타일)는 큰 그림(혹은 청사진)을 제공해야 한다. 결국 해결책을 제시하는 산물이니까 그렇다. 반면에 아키텍처 안에서 반복적으로 발견되는 패턴은 그 자체로 해결책이 되지 못할 수 있다.

An "Architecture Pattern" is generally a pattern that is used inside an "Architecture" to facilitate a way of doing something important there, *inside*.

CTO님 지적대로 Command와 Query 비대칭으로 생기는 중복 발생 가능성에 대한 해법이 없이 CQRS 적용만으로 무엇이 좋아진다고 단정할 수 없다는 말이다. <트레이드오프와 아키텍트 그리고 개발자의 소통 문제> 편에서 다룬 상충되는 문제에 대해서 '그건 몰라'라고 방치한다면 제대로 된 해결책이 아니지 않은가?

DDD와 도메인 모델링

다시 페이스북을 보니 댓글 역시 Command와 Query 사이의 대응 관계를 푸는 법이 설명되지 않으니 지인이 제기한 의문으로 보였다. 응용 프로그램 특히 기업에서 쓰는 응용 프로그램은 기술적인 부분과 비즈니스 부분을 다 알아야 제대로 구현할 수 있다. 이를 녹여내는 과정을 도메인 모델링이라고 부르기도 한다. 갑작스러운 이벤트에 대해 쓴 글을 <도메인 모델링 세미나> 연재에 포함시킨 이유가 그것이다.

모호한 답변이기는 하지만 위에서 '공동체의 행동 양식'이라고 한 부분은 개발자들이 지켜가는 방법이 필요하다는 말이다. 요즘 도메인 모델링과 DDD에 관심이 많은 절친 이일민님도 하루 차이로 페이스북에 DDD에 대한 글을 썼다. 나는 DDD와 '공동체의 행동 양식'이 비슷한 말이라고 생각한다.

다만, DDD 책을 아무리 읽어도 우리 상황에 맞는 답은 나오지 않는다. DDD는 그저 풀이법(그걸 방법론이라고 부르든 말든)과 패턴을 말할 뿐, 도메인이라는 말은 특정 공동체(혹은 조직)의 기술과 비즈니스를 묶은 영역을 지칭하는 말이기 때문이다.


Command와 Query의 대응관계

글이 장황해지고 있는데 다시 화제를 좁혀보자. CQRS는 근본적으로 Command와 Query의 비대칭을 다루는 패턴이다. 입력과 출력이 다른 상황의 대응관계를 잘 정리해보겠다는 시도이다. 이를 다루는 패턴을 나에게 지금 당장 최대한 짧게 정리하라고 숙제를 주자. 당신이 경험 많은 아키텍트라면 답을 해보시라고 말이다.

Domain Event를 체계적으로 정의해서 적절한 위치에(마이크로 서비스 환경이라면 마이크로 서비스별로 필요한 데이터 수정을 담은 메소드를 호출하도록 매칭 시킴) 변화를 가하고, 조회 화면은 헥사고널(혹은 포트 앤 어댑터) 아키텍처를 채용하여 처리하도록 한다
출처: https://engineering.linecorp.com/ko/blog/port-and-adapter-architecture


여기서 지난한 문제는 다양한 상황을 지켜보면서 Domain Event를 체계적으로 정의하는 과정이라고 할 수 있다. 화면 기반으로 프로그램을 짜는 부분은 대부분의 개발자가 익숙하다. 반면에 UML을 요식행위 이상으로 써오지 않았던 지난 20년을 고려하면 우리 개발 커뮤니티에 도메인 모델링 역량은 소수의 머릿속에만 있다고 봐야 한다. 어제 VR 솔루션을 플랫폼화 하는 개발업체 시니어와 만나 나눈 대화도 비슷한 내용을 다뤘다.


작명이 기본이다

다시 이 글을 쓰게 된 동기가 된 두레이 대화로 돌아가자. 나는 CTO와 대화 과정 말미에 개발자들의 작명 역량 부족을 지적했다. 나는 20년 정도 일을 하면서 정의하는 일이 얼마나 중요한지 배우고 또 배운다. Command와 Query의 관계도 개념적으로는 비대칭 관계를 풀어내는 일이다. 관계를 풀기 위해서는 어디까지가 누구의 경계인지 정하는 정의가 우선되어야 한다. 아래 대화를 볼 때, 내 관점 속에 들어 있던 생각이 그랬다.


그래서 상품 마스터라는 모호한 표현은 그대로 방치할 수 없는 (너무나도 널리 쓰이는) 관행이었다.

작명의 문제를 드러내기 위해 도메인(동료가 다루는 문제 영역)의 맥락을 조금 설명한다. 나는 두 개의 전혀 다른 사용자 화면 체계가 있어야 한다고 가정하고, 헥사고널 아키텍처를 채용하여 아래와 같이 그림을 그린다. 동료는 다수의 시스템에서 수집할 수도 있는 레코드를 다루고 있기에 상품 마스터라는 표현은 데이터 원본이 들어 있는 시스템과 최종 출력에서 담기는 데이터의 출처가 복수일 수 있다. 그래서 헥사고널 아키텍처를 채용한 것이다. 서로 다른 조직에서 운영하는 시스템 연동을 전제로 한 마이크로 서비스 아키텍처가 필요한 맥락이다.

Command와 Query의 비대칭

(귀찮았지만 글 쓰는 덕분에) 이렇게 그림을 그려보니 내 머릿속의 말들이 다른 사람들도 알아들을 수 있는 말로 바뀐다. 역시 기록은 중요하다. 설명에 필요한 부분만 담기 위해 업무를 단순하게 조금 왜곡해서 써본다. CBT는 Cross Border Trading의 약자다. 우리는 중국 대상 역직구 서비스를 운영 중이다. 여기서 주문을 받으면 해외로 물건을 보내기 위해 CBT 물류사에 의뢰를 해야 한다. 그 과정의 끝을 '송장 접수'라고 단순화해보자.

송장 접수는 물류 창고를 운영하는 업체 입장에서는 일종의 물류 주문(요청)일 수도 있다. 물류 업체 입장에서 주문을 받았다고 해서 물건이 바로 도착하는 것은 아니다. 우리거 거래하는 창고에서 일하는 사람 입장에서는 눈에 보이는 물건을 기준으로 일하는 것이 현재 프로세스라고 해보자. 그렇게 되면 박스부터 확인하고 송장을 보고 이 물건을 어떤 단위로 어떤 수단(항공편 or 배편)으로 언제까지 어디로 보낼지 판단하여 필요한 행동을 한다.


동료가 말하는 상품 마스터는 이 과정에서 주문받은 물류는 어떤 상품을 다루는 것인지 정보가 필요하다는 말이다. 그런데 이를 상품 마스터라고 불러야 할까? 물류 업체 단독으로 쓰는 시스템이라면 별 문제의 소지가 없다.[1]


연계시스템과의 공존을 고려한 마이크로 서비스 환경이라면 혼선을 빚어내는 원천이 된다. 물류 서비스 입장에서 보면 상품은 수많은 고객에게서 사전에 알 수 없는 정보를 받아야 한다. 그들의 형식이 모두 다르기 때문에 오히려 자신들이 필요한 정보만 추리는 일이 필요할 것이다.


나는 이를 아래 그림에서 상품 View라고 묘사했다.


View와 Master 테이블의 차이

이제 마무리로 향하기 위해 설명하지 않았던 두레이 대화의 배경을 조금 더 설명해야 할 듯하다. 동료와 나눈 두레이 대화가 달린 두레이 업무명은 이렇다.


물류 협력사 DB 재고 조회 대상을 테이블에서 View 테이블로 변경한다.


내가 위의 그림을 그릴 때 View라고 쓸 때는 DB 오브젝트인 View를 떠올린 것이 아니라 물류 창고 사용자라고 쓴 사용자들이 보고 싶은 데이터 요구를 담은 말이다. 그야말로 그들의 관점을 개념적으로 쓴 말이고, CQRS의 Query들을 하나의 유형으로 묶을 수 있다는 전제의 개념이다. 그런데 놀랍게도 DB 오브젝트 View도 그렇게 쓸 수 있다. (성능 문제는 논외로 하면)


그런데 동료가 View 생성 대신에 별도의 테이블로 만들어달라고 한 것이다. 왜 그랬을까? 그의 말에 단서가 있다.

상품 목록 리스트가 없어서, 입고된 리스트에서 중복을 제거하고 만들어 사용했다기에 유사한 방법으로 stock table record생성하니까 stock table을 이용해 보자는 제안입니다.

매번 송장 기준으로 상품 목록을 추리다 보니 재고(Stock) 테이블에서 해결할 수 있다면 좋겠다는 바람인 듯하다. 창고에서 일하는 입장에서는 의뢰받은 물건이 어떤 상품인지 처음부터 알 수 없다. 그래서, 참조 형태로 상품을 알 수 있으면 좋을 텐데, 이를 지칭하는 말로 '상품 Master'라는 낡은(?) 표현을 쓴 것이 아닐까 싶다.


Command와 Query의 대응의 힌트

이제 마무리를 할 수 있을 듯하다. CTO님이 대화에서 썼던 View라는 표현이 있다. Query 구현체를 그렇게 부른 것이 아닐까 싶다. 그런데 바로 앞에서 내가 설명한 상황이 다름 아닌 Command와 Query 비대칭의 사례이다. 물류 회사 입장에서 (느슨하게 말해서) 박스 단위로 배송 의뢰를 하는 일을 CQRS 패턴을 적용하면 Command 형태로 시스템에 반영한다. 그런데 그 Command 처리 과정에서 송장(DDD의 Aggregate) 처리 과정에서 주문(물류 의뢰) 상세에 해당하는 상품 Entity(역시 DDD 빌딩 블록) 수정도 필요하다.


그리고 동료의 댓글을 보면 이런 Command 처리와는 전혀 별개로 '상품 목록 리스트'를 보는 Query가 필요하다. 이 정도면 CQRS를 소재로 했지만, 적어도 도메인 모델링이 중요한 이유를 설명하는 범용적인 사례가 될 수 있겠다 싶어 빠르게 글을 쓴다.


여기서 내가 굳이 헥사고널을 채용한 이유는 아래 그림으로 설명할 수 있다. 이때 CQRS의 Q영역은 포트(혹은 사용자층)가 어디냐에 따라 다른 View(혹은 Query 유형)로 보여줘야 한다.


급하게 써서 오류가 있을 수 있지만 아침부터 피드백을 구두로 대충 하는 대신에 글로 하니 뿌듯하다. :)


주석

[1] 왜 그런지 의문이 나는 분에게는 이름공간(namespace)에 대해 학습하기를 권한다.


지난 도메인 모델링 세미나 연재

1. 도메인 모델링? 비즈니스 모델링 어떻게 하나요?

2. 도메인 모델링 활용의 기본 아이디어

3. 데이터 제품 접근방식과 그 표현

4. 아키텍처는 의사소통에 관한 문제라서?

5. 아주 이상적인 아키텍처

6. 모델 저장소의 의미와 구현

7. 리팩토링을 내장할 수 있다면?

8. Repository 의미 더 찾아 보기

9. 도메인 모델링에서 맥락은 왜 필요한가?

10. Repository 빌딩 블록에 대해 생각해보기

11. 맥락은 어떻게 표현할 것인가?

12. 경계 설정은 소프트웨어 설계의 핵심 활동

13. Context와 컴포넌트와 이상적인 아키텍처

14. Context와 그 시점 문제인지 여부

15. 개체(시스템)의 재설계와 경계의 변경

16. 도메인 모델링은 어떻게 하는가?

17. 기능을 함수로 포착하고 맥락을 표현하기

18. 도메인 모델링 절차에 대하여

19. 왜 도메인 모델링을 하는가?

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari