본문 바로가기
프로그래밍/기타

CQRS(Command and Query Responsibility Segregation) 맛보기 - 이론편

by 사바라다 2022. 6. 16.

안녕하세요. 오늘은 CQRS 패턴이란 무엇이고 어떻게 구현하며 이 패턴으로 시스템을 개발하였을 때 어떠한 장점과 단점을 가지는지에 대해서 알아보는 시간을 가져보도록 하겠습니다.

DDD와 CQRS

CQRS란 무엇인가에 대해서 이야기 하기전에 앞서서 우리는 먼저 DDD에 대해서 이야기를 할 필요가 있습니다.

간단히게 DDD에 대해서 설명하고 넘어가도록 하겠습니다. DDD는 Domain Driven Design의 약자로 어플리케이션을 비즈니스 Domain별로 나누어 설계 및 개발을 진행하는 개발 방법론입니다. 여기서 Domain은 비즈니스를 말합니다. 그리고 DDD에서는 비즈니스 중심으로 개발하면서 비즈니스에 매핑되는 도메인 모델을 가지게 되는데 이는 비즈니스 자체를 추상화한 설계도로써 도메인 서비스의 중심으로 동작하게됩니다. 이렇게 비즈니스 중심으로 개발을 하게되면 아무리 복잡한 비즈니스라도 그에 매핑되는 도메인 모델을 가지게되고 장기적으로 보았을때도 관리하기 좋기 때문에 DDD를 통해서 개발을 진행한다고 할 수 있습니다.

그렇다면 DDD를 따라서 비즈니스 중심의 도메인 모델을 만들면 모든 비즈니스에 대해서 안정적이고 만족할 수 있었을까요 ? 아쉽게도 그렇지 않았다고 합니다. 특히 요즘처럼 고차원의 UX, 급변하는 비즈니스 모델, 그리고 매번 달리지는 요구사항에 대해서 안정적이고 계속해서 만족감을 줄 수 있는 도메인 모델은 과연 만들 수 있을것인가하는 의문이 남습니다. 여기에 Greg Young이라는 사람에 의해서 CQRS가 처음 2010년 언급이 되고 등장하게 됩니다.

기존의 도메인 모델은 모델의 생성, 수정, 삭제 그리고 읽기가 모두 하나의 모델을 기본으로하여 사용되는고 있었습니다. 하지만 생성, 수정, 삭제 비즈니스와 읽기를 위한 모델이 공존하다 보니 해당 모델의 복잡도가 지속적으로 지속적으로 증가된다는 사실을 알게 되었다고합니다. 그래서 생성, 수정, 삭제에 대한 CUD 모델과 읽기의 R 모델을 구분하여 모델의 복잡도를 일정하게 유지시키기 위해서 나온것이 바로 CQRS(Command and Query Responsibility Segregation)입니다.

일반 도메인 모델

위의 이미지가 일반 도메인 모델에 대해서 각 요청이 오고가는 것을 나타낸 것입니다. 하나의 도메인 모델을 이용하여 생성과 수정, 그리고 읽기가 모두 오고가는 것을 나타내고 있습니다. 여기서 Repository는 도메인 모델을 저장하는 저장소라고 생각해주시면 됩니다.

대부분 모델의 경우 이렇게 CRUD를 하나의 모델을 통해서 진행하더라도 아무런 문제를 가지지 않습니다. 오히려 이러한 일반적인 도메인이 단순하며 단단한 모델일 수 있습니다. 하지만 정말 모델이 복잡해지거나 기존의 모델만을 이용해서는 특정 비즈니스 문제를 해결하기 어려질 수 있습니다. 가령 최종 상태가 아닌 전체적인 모델 변화에 대한 히스토리를 보여주어야 한다 등의 문제가 있을 수 있습니다. 이럴때라면 우리는 CQRS를 고려해볼 수 있습니다.

CQRS 모델

CQRS 패턴을 사용할 때 저장소(Repository)를 동일한 것을 사용할 수 있고, 서로 다른 저장소를 사용할 수 도 있습니다. 위 이미지의 왼쪽은 동일한 저장소를, 오른쪽은 서로 다른 저장소를 사용하는 것을 나타낸 것입니다. 양쪽 모두 CUD에 대한 모델과 R의 모델이 다르다는 사실 또한 위 이미지를 통해서 알 수 있습니다.

또한 CQRS 패턴을 제대로 사용하기 위해서는 아래의 개념을 명확하게 가지고 구현을 하여야합니다. Aggregate와 Projection입니다. 각 요소에 대해서 알아보도록 하겠습니다.

Aggregate / Aggregator

먼저 Aggregate는 도메인 모델에서 로직적으로 그룹을 이루는 Entity와 Value의 집합니다. Aggregate는 여기서 Command 모델의 집합이라고 할 수 있습니다. 이 집합은 비즈니스 원자성을 가져야합니다. 비즈니스 원자성을 가진다는 것은 비즈니스 로직이 실행 됬을 때 연관 되어있는 도메인 모델에 비즈니스의 일부만 적용되는 상황이 있었서는 안된다는 것이빈다. 이를 위해서 연관되어있는 Entity들 사이에 Transaction 관계가 있어야만합니다.

Projection / Projector

Projection 이라는 것은 Command 모델을 Query Model로 변환하는 것을 말합니다. CQRS 패턴을 적용한다는 것은 Command 모델이 저장이 완료되었을 때 또는 저장과 동시에 Query 모델 또한 저장하겠다는 것을 말합니다. 그러기 위해서는 Query 모델에 맞게 Command 모델을 변환하는 것이 필요한데 이것을 Projection이라고 합니다.

CQRS 동기화 문제

Command 모델과 Query 모델이 다르기 때문에 해결해야하는 문제가 있습니다. 바로 동기화 문제입니다. 2개의 서로 다른 모델을 동기화해야하기 때문에 고민이 필요한 부분입니다. 동기화 문제를 생각할 때 Command 모델에는 적용이 되었는데 Query 모델에는 적용이 안되거나 할 경우가 생길 수 있습니다. 이런 문제를 어떻게 해결할 수 있을지 고민이 필요합니다.

가장 단순한 방법은 두개의 모델을 저장하는 것을 하나의 Transaction로 묶어내는 것입니다. 그렇게 한다면 Command 모델에만 들어가고 Query 모델에는 안들어가는 일이 없이 잘 동기화 될 것입니다. 하지만 여기에도 문제가 있습니다. 만약 서로 다른 저장소를 사용한다면 이는 불가능할 것입니다. 추가적으로 DDD의 관점에서 서로 다른 Aggregate는 서로 다른 라이프 사이클을 가지고 도메인 로직이 복잡해지기 때문에 Transaction으로 묶는것을 권장하지 않습니다. 따라서 정석대로 한다면 Command 모델의 저장에 의한 이벤트를 이용해서 Query 모델에 데이터를 동기화하는 방법을 사용해야합니다. 물론 이는 정석일 뿐이며 판단하여 Transaction 사용이 가능하고 타당하다고 한다면 그렇게 진행하는것도 좋다고 생각합니다 :)

이렇게 Event를 통한 동기화를 하게 된다면 상황에 따라서 Event의 누락으로인해 모델간의 동기화가 순식간에 잘 이루어지지 않을 수 있습니다. 이렇기 때문에 모델의 일관성은 결과적 일관성(Eventually Consistency)을 기본으로하게 됩니다. 그리고 유실 없이 100% 동기화를 항상 만족해야한다면 아웃박스패턴(Out-Box-Pattern)을 사용하는것을 고려야해야할 수도 있습니다.

CQRS의 장점과 단점

장점

CQRS로 얻을 수 있는 장점을 간단히 나열해보도록 하겠습니다.

  • CQRS의 가장 큰 장점이라고 하면 쓰기 모델과 읽기 모델을 분리함으로써 하나의 모델이 었다면 복잡했을 도메인 모델을 단순하게 가져갈 수 있다는 점
  • 쓰기와 읽기를 서로 다른 Repository를 사용할 수 있으며 이로써 각 Repository의 장점을 살릴 수 있습니다. 예를 들어 읽기 모델을 Redis를 Repository로 사용하면 key-value 형식의 모델로 저장해둔다면 빠른 읽기를 기대할 수 있습니다. 반대로 쓰기 Repository를 Cassandra나 scylla와 같은 컬럼 지향 DB를 사용한다면 대량의 쓰기에 대해서 빠른 쓰기를 기대할 수 있습니다.

하지만 역시 이에 따른 단점도 명확합니다.

  • 가장 큰 단점은 전체적인 시스템의 복잡도가 많이 올라간다는 것입니다. 사실 하나의 도메인을 사용하는게 더 단순한 경우가 대부분입니다.
  • 쓰기 모델과 읽기 모델의 100% 동기화를 위해서는 추가적인 모델을 사용하거나 위에서 언급했던 별도의 동기화 전략이 필요합니다.

마무리

오늘은 이렇게 CQRS의 이론에 대해서 알아보는 시간을 가져보았습니다.

도메인 복잡도와 시스템 복잡도의 밸런스를 어떻게 잘 잡고 CQRS를 적용할지가 정말 중요할 것 같습니다.

다음 시간에는 간단하게 CQRS를 kotlin을 이용하여 구현해보는 시간을 가져보도록 하겠습니다.

그리고 오늘 잠깐 이야기한 DDD에 대한 내용은 이후 별도의 제대로된 포스팅에서 다시 이야기해보도록 하겠습니다.

감사합니다.

참조

[1] https://www.baeldung.com/cqrs-event-sourcing-java

[2] https://martinfowler.com/bliki/CQRS.html

[3] https://youngjaekim.wordpress.com/2016/09/12/%EC%B5%9C%EC%8B%A0-%EA%B8%B0%EC%88%A0-cqrs-%EC%B2%98%EC%9D%8C-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0/

[4] https://docs.microsoft.com/en-us/archive/msdn-magazine/2015/june/cutting-edge-cqrs-for-the-common-application

댓글