Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions chap11/김재연.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# CQRS

## 단일 모델의 단점

조회할 때는 생각보다 많은 것들을 고려해야한다.

주문 내역을 조회한다고 하더라도, Product, Member, Order등 총 3개의 애그리거트를 참조해야 하는데, 이를 연관관계를 끊어내기 위해, 간접참조로 진행한다고 하면, 즉시 로딩과 같은 최적화 기능을 사용할 수 없고 이는 성능 문제를 이야기한다.

또한, 직접 참조로 연결하더라도 문제가 생기게 되는데, 조회 화면 특성에 따라 같은 연관도 즉시 로딩이나, 지연 로딩으로 처리해야하기 때문이다. (복잡도를 증가시키는 것 같다.)

또한, DBMS가 제공하는 전용 기능이 필요하면 JPA의 네이티브 쿼리를 사용해야 할 수도 있다. (힌트 같은 것들?)

이런 고민이 발생하는 근본적인 원인은 무엇일까? 바로, 단일 도메인을 사용하기 때문이다. 객체 지향으로 도메인 모델을 구현할 때, 주로 사용하는 ORM기법은 상태 변경 기능을 구현하는 데는 적합하지만, 여러 애그리거트에서 데이터를 가져와 출력하는 기능을 구현하기에는 복잡하다.

이를 해결하기 위해 상태 변경을 위한 모델, 조회를 위한 모델을 분리하는 것이고, 이것이 바로 CQRS이다.

## CQRS

시스템이 제공하는 기능은 크게 두 가지로 나뉠 수 있는데, 하나는 상태를 변경하는 기능 하나는 상태 정보를 조회하는 기능이다.

도메인 모델 관점에서 상태 변경 기능은 주로 한 애그리거트의 상태를 변경하게 된다.

하지만, 조회를 진행할 때를 생각해보면 여러 애그리거트가 필요할 때가 많다.

이는 상태를 변경하는 범위와 상태를 조회하는 범위가 정확하게 일치하지 않는 문제로 정의할 수 있고, 단일 모델로 이를 구현하게 되면 당연히 이 문제를 그대로 떠안게 될 것이다.

이를 해결하는 방법이 CQRS(Command Query Responsibility Segregation)이다. 즉, 단순하게 말하면 조회를 위한 모델, 명령을 위한 모델을 분리하는 것이다.

![](https://i.imgur.com/mSjXvEy.png)

만일, 온라인 쇼핑에서 다양한 차원의 주문 판매 통계를 조회해야 한다고 해보자. 그러면 단일 모델에 성능 관련 JPA의 기능을 덕지덕지 붙여야 할 것이고, 점점 단일 모델의 복잡도는 높아질 것이다.

그래서, 이를 CQRS를 적용해서, 통계를 위한 조회 모델을 별도로 만들어, 명령은 JPA로, 조회는 MyBatis를 선택한다는 등의 시도를 진행 해볼 수 있다.

![](https://i.imgur.com/Ci8Kpb2.png)

잘 보면 응용 서비스가 존재하지 않는다. 조회자체는 굉장히 단순한 편이 많기에, 이렇게 구현할 수 있는데, 만일 복잡하다면 응용 서비스를 두고 도메인 로직을 구현해주면 된다.

![](https://i.imgur.com/b6BpGyb.png)

굉장히 좋은 예이다.

OrderSummary와 같은 것은 조회 모델이기에 모든 데이터가 담겨져있고, 그렇지 않은 상태 변경을 위한 도메인 모델은 우리가 지금까지 설계해오던 애그리거트의 모습을 하고 있다.

OrderSummary와 같은 것들은 네이티브 쿼리를 사용하여 조인해서 한번에 데이터를 가져온다던가 하면 된다.

![](https://i.imgur.com/8lokBbG.png)

위 말은 다음과 같이 리플리케이션을 진행한다는 것인가? 찾아보면서 생각해보니 맞는 것 같다.

![](https://i.imgur.com/8OiV87D.png)

뭐야 바로 아래에 있었네, 이벤트로 처리한다고 한다. 근데 위에서 내가 했던 것처럼 리플리케이션 해도 되지 않을까? 아닌가? 애초에 다른 종류의 DB라서 리플리케이션이 되지 않는가?

![](https://i.imgur.com/YKJCMM7.png)

> 일반적으로 마스터-슬레이브(Master-Slave) 데이터베이스 구조에서 마스터와 슬레이브는 같은 종류의 데이터베이스 시스템이어야 합니다. 이는 일관된 데이터 복제와 동기화를 유지하기 위해 필요합니다. 예를 들어, 마스터가 MySQL이라면 슬레이브도 MySQL이어야 하며, 동일한 데이터 구조와 쿼리 언어를 사용해야 합니다.
>
> 하지만 서로 다른 종류의 데이터베이스를 사용하는 사례도 존재합니다. 이러한 경우는 보통 "이종 데이터베이스 복제(Heterogeneous Database Replication)"라고 불리며, 일반적인 마스터-슬레이브 설정과는 다릅니다. 이종 복제에서는 마스터 데이터베이스(MySQL 등)에서 변경 사항을 캡처하여 슬레이브(NoSQL 등)로 변환하는 별도의 복제 도구나 미들웨어를 사용합니다. 이러한 설정은 특정 요구사항에 따라 데이터를 다양한 형식으로 저장하거나 분석할 때 사용됩니다.
>
> 그러나 이 방법은 복잡하고, 데이터 일관성을 유지하는 데 어려움이 있을 수 있으며, 모든 데이터베이스 간에 가능한 것은 아닙니다. 또한 실시간 동기화보다는 비동기화 방식으로 작동하는 경우가 많습니다.
>
> 따라서, 마스터와 슬레이브가 다른 종류의 DB일 수는 있지만, 이는 일반적인 설정은 아니며 특별한 요구 사항과 도구가 필요합니다.

피티 형의 대답이다. 힘들다고 한다. 그래서 그냥 이벤트로 하는게 맞는듯 헤헷

그래서, 동기화 시점을 맞춰주기 위해 이벤트를 사용할 경우, 특성에 따라 동기로 진행할지, 혹은 비동기로 진행할지 선택할 수 있다. 동기 이벤트 혹은 글로벌 트랜잭션을 사용하여 처리하여 확실하게 명령, 조회 DB를 동기화 시킬 수 있지만 성능이 떨어진다는 단점이 존재한다.

### 웹과 CQRS

백엔드 개발자라면 당연히 알 것이다. GET Method가 다른 Http Method보다 훨씬 많이 일어난다는 것을, 그렇기에 Command에 투자하는 리소스, Query에 투자하는 리소스는 분명히 다르게 들어가야한다.

캐싱을 적용하든, 쿼리를 최적화하든, 조회 전용 저장소를 만들거나 별짓을 다한다. 사실 이것들을 적용하게 되면 알게 모르게 CQRS를 적용하는 것과 같은 효과를 만들게 된다.

그러니, CQRS를 적용하는 것과 같은 효과를 만드는 것뿐만 아니라, CQRS를 적용해 이를 조금 더 명시적으로 적용해보자.

### CQRS 장단점

CQRS 패턴을 적용할 때 얻을 수 있는 장점은, 명령 모델을 구현할 때 도메인 자체에 집중할 수 있다는 점이다. 아무래도 명령, 조회 기능을 하나의 모델에 구현하게 되면 복잡도가 당연히 향상할 수 밖에 없다. 이를 분리하니 당연히 복잡도는 줄어들 수 밖에 없다.

또한, 이전에 이야기 했던 것과 같이 조회가 훨씬 많이 일어나니 Query 부분에 리소스를 엄청나게 많이 들일 수 있다.

캐시 기술을 적용하거나, 쿼리를 최적화하거나 조회 전용 저장소를 대폭으로 늘릴 수도 있다. 이렇게 적용하더라도, 이는 명령 모델에 전혀 영향을 주지 않는다.

하지만, 구현할 코드양이 많아진다. 그렇기 때문에, 서비스의 특성이 도메인이 복잡하지 않다거나, 대규모 트래픽을 감당할 필요가 없다면 굳이 이렇게 CQRS를 활용할 필요가 없을지도 모른다.

두 번째는 더 많은 구현 기술이 필요하다는 것이다. 동기화를 위해 메시징 시스템을 도입해야 할 수도, 혹은 조회 모델과 명령 모델의 구현 기술을 다른 구현 기술을 사용하기도, 다른 저장소를 사용하기도 한다.

이러한 장단점을 고려해서 도메인이 복잡하지 않거나, 트래픽이 높지 않은데 굳이 CQRS를 도입해서 복잡도를 높이지는 말자.