게시글 작성/댓글/인기글/좋아요/조회 등 각각 도메인에 대해 독립적인 데이터베이스와 서비스를 구성한 MicroService Architecture의 기본적인 골격을 드디어 완성하였다.

다음 단계는 게시글 내역 조회로, 게시글에 해당하는 댓글, 좋아요 등의 모든 정보를 조회 및 추출하는 기능에 대한 설계이다.
그런데 여기서 드는 의문점은, 게시글 조회 시 댓글 및 좋아요, 조회수 내역에 대해 모두 추출해야한다면, article Service에서 조회 후에 article_Id를 활용하여 댓글, 좋아요, 조회수까지 조인(조합)하여 줄줄이 데이터를 추출해야 하는가에 대한 것이다.
물리적으로나 구조적으로나 데이터 관리방법이 다르고 도메인이 다른 데이터를 어떻게 조회해야 하는지, 데이터 조회를 최적화할 수 있는 방안에 대해 고민한 과정을 기록한다.
위의 문제는 한마디로 표현하자면, 기능적으로 한 쪽에만 트래픽이 몰려 불균형 상태가 예상되는 상황이다.
즉 Article 도메인에서 게시글을 작성하는 서비스보다 게시글을 조회하는 서비스의 트래픽이 훨씬 많다.
뿐만 아니라 게시글을 조회할때 단순 게시글 뿐 만 아니라, 게시글에 연결되어있는 댓글/좋아요/조회수 기능을 일일이 요청하면서 데이터들을 종합해야하는 등 서비스 최적화가 필요한 문제를 직면하기도 하였다.
지금까지 알고 있었던, 단순히 기능의 특성(Command&Query)별로 구분하는 것은 결코 CQRS가 아니다.
독립적으로 흩어진 데이터들을 알맞게 조합하면서, 트래픽이 병목할 것으로 예상된 서비스를 구조적으로 어떻게 확장하고 최적화할 것인지에 대한 고민을 시작으로, 서비스 등의 아키텍칭과 데이터베이스 등의 데이터모델링 등 프로젝트 전역적으로 유지관리를 효과적으로 할 수 있는 방안 중 하나이다.
일단, 현재의 서비스 구조를 살펴보고 최종적으로 어떠한 전략을 적용하면 좋을지 살펴보도록 한다.

현재 프로젝트는 위 구조처럼 게시글 전내역 조회 시, Article Service의 조회서비스를 호출하여 댓글/좋아요/조회수 데이터를 각 데이터베이스로부터 추출, 조합하는 방식으로 이루어진다.
이 프로젝트 구조로 유지관리를 지속한다면,
위와 같은 여러 곤란한 상황들이 발생할 것이며, 한 Service에 몰릴 트래픽에 따라 Service의 기능과 데이터베이스의 데이터 모델링을 구분하는 CQRS Pattern을 적용해보고자 한다.
최종적으로 각 독립적인 데이터베이스로부터 데이터를 추출하고 조합하기 위해 쓰기와 읽기 서비스를 분리한다.
다만, 단순히 쓰기 작업과 읽기 작업을 구조적으로 분리하는 것에 지나지 않고 데이터모델링 관점에서도 고려해야할 점이 많다.
이러한 여러 고민점들이 있었기에 이 글을 작성하게 되었고, 어떤 고민 과정이 있었는지 찬찬히 살펴보겠다.
CQRS의 개념을 살펴보면, Command Query Responsibility Segregation으로 "Command(쓰기)와 Query(읽기)책임의 분리"라 할 수 있겠다.
또한 분리수준을 Class/Package/Service/DataModeling으로 나누어, 트래픽 등 상황에 맞게 적절한 분리를 진행할 수 있다.

일단 Service를 쓰기/조회 작업으로 나누어 진행해보고자 한다.
1차적으로 Article Read Service의 서비스를 분리하여, 이를 통해 Article Service/Like Service/Comment Service/View Service로부터 게시글 내역에 대한 연관 데이터들을 추출해온다.
Article Service라는 하나의 서비스 도메인에서 쓰기/읽기 작업이 이루어졌던 이전과 달리, Article Read Service를 분리하여 양방향 의존 및 순환참조를 제거하였다.
이에 따라 각 MSA는 독립적인 관리가 가능하고, 오직 게시글 조회만을 위한 최적화 목적의 Article Read Service를 안정적으로 구축 및 독립적 확장을 할 수 있겠다.
하지만 아쉽게도 이 구조에서도 약간의 문제가 존재하는데, 각 데이터가 도메인마다 독립적으로 분리되어 있기 때문에 네트워크 비용과 서비스로의 부하 전파, 데이터 조합 등의 비용이 발생하여 꽤 번거로운 후속 조치를 진행해주어야 할 것이다.
따라서 CQRS는 단순히 Package/Class/Service를 나눈다고하여 이루어지는 것이 아니고, 트래픽 불균형 및 데이터 조합 시 발생하는 불필요한 비용을 감소하는 것까지 전제한 아키텍칭적 요소임을 분명히 인지해야 한다.
CQRS를 넓은 의미로, 추상적으로 표현해보자.

일전에 Command 서버에서 한꺼번에 게시글 관련 데이터를 추출할 경우 장애전파 및 확장성(유지관리) 측면에서 불리하므로 Service를 분리하였다.
그리고, 최종적으로 해당 Service를 위한 독립적인 data modeling 및 data 구축이 필요하다는 결론까지 도달하게 되었다.
이 Query 책임을 보유하고 있는 Article Read Service에 대해서도 결국엔 독립적인 데이터 관리가 필요하고, 이 역시 MSA 구축이 필요하다는 점이 중요하다.
결국 Service 분리를 넘어, Package/Class/DataModeling까지 전역적인 분리가 필요하기에 이에 대한 독립적인 관리 체계를 어떻게 구성할 것인지 세부전략에 대해 고민해보았다.
Query 도메인에서 실시간으로 변경사항을 반영해오기 위해선
의 두가지 방안이 떠올랐고, 이때 재활용성이 크기도 하고 안정적인 실시간 처리가 가능한 Kafka를 적극 활용해보고자 하였다.

즉, 게시글/댓글/좋아요 서비스에서 이벤트를 publish하면 Kafka Broker는 이를 받아들여 게시글 조회서비스에 이 이벤트를 반영(Consuming)한다.
이때 Kafka Broker에서 이를 Consuming할 Consumer group을 별도로 구성하여 기존 동일한 partition 데이터를 처리하더라도, 향후 있을 조회서비스의 데이터 추출 목적으로 다른 형태로 데이터를 처리하도록 구성한다.
나아가, 데이터 모델이 필요한 데이터 구축 및 관리방안에 대해 고민해보았다.
복잡하게 생각할 필요없이, Query 도메인에서 필요한 Query Model은 결국 어떠한 데이터를 필요로 하며 조합해야 하는지에 대한 결과이다.
즉, 분산데이터의 조합 및 질의가 필요한데 이때 Query의 조인비용을 줄이고 조회최적화를 위해 필요데이터가 어느정도는 비정규화되어있어야 의미있는 최적화 성능을 기대할 수 있겠다.
일단 Query Model은
로 구성해보았고, 이 경우 조인할 필요없이 필요데이터를 모두 조회할 수 있는 단건조회의 최적화 방안이라 생각하여 향후 추가적인 최적화 시 이 Query Model에서 시작하면 좋을 것으로 보았다.
(유의, 조회수는 Redis에서 가져올 수 있기에 ArticleQueryModel에서는 제외)
참고로 Query Model에 필요한 데이터는 메모리에 저정하여 빠른 조회성능을 기대해보고자, In Memory database를 활용해 볼 생각이다(Redis).
이때,
참고로 Query Model에 대해 좀 더 세부적으로 알아보았는데, CQRS Pattern을 위해 도입한 개념인지 다른 실무에서도 중요하게 쓰일 수 있는 개념인지 이참에 확실하게 짚고 넘어가고자 한번 공부해보았다.
결론적으로, Query Model은 CQRS에서 핵심적인 개념이긴 하지만, 그 의미가 “조회만 담당하는 데이터 구조”라는 좁은 개념으로만 쓰이지 않는다.
CQRS(Command Query Responsibility Segregation)에서 핵심은 “쓰기(Command)”와 “읽기(Query)”의 모델을 분리한다는 것이고, “읽기(Query)” 전용으로 최적화된 데이터를 Query Model이라 지칭한다.
즉, Query Model이라는 용어 자체는 CQRS 문맥에서 주로 등장하기는 한데, 더 넓은 본질적인 개념으로 보았을때 CQRS 이외 시스템에서도 다음과 같이 비슷한 개념이 존재한다.
Read Replica (RDB복제본, 읽기 전용을 위해 원본데이터 복제)
→ 읽기 전용 DB로 조회 트래픽을 분리하는 것은 일종의 Query Model 설계.
Search Index (Elasticsearch 등)
→ 검색이나 조회에 최적화된 별도 모델을 구성.
API Aggregation Layer
→ DDD에서 여러 서비스를 호출하여 View용 DTO로 구성하는 것도 Query Model의 확장 개념.
즉, “Query Model”이란 단어는 CQRS에서 유래했지만,
그 개념(읽기/조회 전용의 별도 모델 구성)은 다른 아키텍처에서도 폭넓게 응용할 수 있겠다.
CQRS에서 Query Model은 “조회 결과를 구성하기 위해 필요한 데이터 구조 및 소유권의 단위”를 말한다.
즉, 단순히 “화면에 어떤 데이터를 보여줄까” 수준이 아니라,
“이 데이터를 어떻게 조합하고 어떤 저장소 혹은 서비스에서 가져올까”까지 포함하며 일전에 고민한 과정이 그대로 Query Model에 녹아져있다고 보아도 무방하다.
좀 더 구체적으로 살펴보자.
1) 데이터 구조 관점 — “무엇을 보여줄 것인가”
{
"articleId": 123,
"title": "CQRS 패턴이란?",
"author": "효균",
"commentCount": 14,
"viewCount": 2671
}
데이터모델링 관점에서 보았을때는, "어떠한 데이터를 보여줄 것인가", 쉽게 말하면 key값으로 대표할 수 있는 데이터 내역으로 보면 이해가 용이할 것 같다.
Query Model은 클라이언트(혹은 소비자)가 보게 될 데이터를 정의하는 명세서 관점에서 볼 수 있다(API의 Response DTO 또는 Read Model Schema 수준).
2) 데이터 조합(Composition) 관점 — “어떻게 가져올 것인가”
이제 이 데이터를 application level에서 어떻게 조합할지에 대한 관점으로 Query Model을 이해해본다.
간략하게,
이를 하나의 Query Model (조회용 모델)로 조합하여 최종적으로 필요한 정보를 추출해야 하며, 이 조합을 담당하는 컴포넌트가 흔히 Read Service / Aggregate Service로 구성한다.
즉, Query Model은 단순히 “화면용 데이터 구조”로 보기 보다는, 여기에서 더 나아가
“조회에 필요한 데이터를 어떤 방식으로 구성할지”에 대한 설계 단위이기도 하다.
전체적으로 내용이 결국 CQRS 패턴의 본질과 최종적인 시스템 구축방향과 일치하고 있음을 알 수 있기에, 한쪽 관점에서만 바라보는 것이 아니라 구조+과정의 모든 관점에서 바라보는 것이 필요함을 기억해두자.
또한 우리는 Kafka Broker를 통해 실시간 이벤트를 처리하면서, 최종적인 데이터 일관성도 유지해야 하는 Eventually Consistency도 함께 보장할 수 있는 구조이다.
DDD에서는 Aggregation이 CQRS Pattern을 구현하기 위한 하나의 방안이다.
현재 프로젝트와는 거리가 멀지만, 향후 실무적으로 많이 쓰이는 개념이기도 하고 헷갈릴 수 있는 부분이 많이 존재할 수 있기에 Aggragation Pattern도 같이 정리해두도록 한다.
Aggregate(애그리거트)는 DDD(Domain-Driven Design) 의 개념이며,
핵심은 “도메인 내 불변식(invariant)/책임의 일관성을 유지하기 위한 일관성 경계”를 설정하는 것이다.
DDD에서는 보통 특정 책임의 객체를 빌려와 사용하는데, 이는 해당 책임을 그 객체에게 위임해주는 것이지, Facade처럼 조합의 개념이 아니다.
즉, 쉽게 말하면 Aggregate Pattern이란 비즈니스 트랜잭션 단위를 정의하는 도메인 객체 집합을 말하며(즉 쓰기 도메인), 조합의 개념보다는 책임의 일관성/위임의 개념이다.
ArticleAggregate
├── Article (root)
├── List<Comment>
├── List<Like>
DDD에서 ArticleAggregate를 위와 같이 설정해주었다면(CQRS Pattern 적용을 위해), Article이 Root Entity, 하위의 Comment나 Like는 이 Aggregate 내부에서만 일관성 있게 관리된다.
즉, 각 표현/설계의 책임을 엔티티 별로 나누어 관리하겠다는 것이 DDD, Aggregate의 핵심이다(이 분리하는 과정이 DDD의 핵심).
이때 root Entity의 의미는, “댓글을 추가할 때 반드시 해당 게시글이 존재해야 한다” 같은 규칙을 하나의 Aggregate 내부에서 보장하는 전제조건이기도 하다(=데이터 유효성 검증).
→ 즉, Aggregate는 DDD에서 명령 시 책임을 위임하여 처리하는 쪽의 개념,
데이터 일관성(Consistency) 과 도메인 규칙(Integrity) 보장에 대한 내용이다.
Facade와 좀 헷갈린 부분이 있었는데, 본질적으로 다르다는 점에 유의한다!(Aggregate는 명령처리를 위해 존재하는 객체이고, Query Model은 조회처리를 위한 뷰)
| 구분 | Aggregate | Facade |
|---|---|---|
| 핵심 역할 | 도메인 규칙의 일관성 유지 | 여러 하위 서비스를 감싸서 단일 인터페이스 제공 |
| 초점 | 트랜잭션 경계, 비즈니스 불변식 | 복잡한 의존 관계 단순화 |
| 위치 | Domain Layer | Application Layer |
| 예시 | article.addComment(user, content) | ArticleFacade.createArticle(dto) |
=
Aggregate은 “명령(Command)을 처리하는 주체”
Query Model은 “조회(Query)를 최적화하는 별도 뷰”
이제 CQRS이 왜 필요하며, 어떠한 전략을 통해 구성할 수 있는지 충분히 이해하고 숙지할 수 있게 되었다.
또한 단순히 구조적 분리를 넘어, 데이터 모델링 측면에서도 별도 관리하여 프로젝트의 확장성과 유지관리성을 확보할 수 있는 중요 전략 중 하나라는 것을 알 수 있었다.
나아가 CQRS 전략 도입을 고민하는 과정에서 여러 중요한 개념들도 같이 배울 수 있었는데, 앞으로 실무적으로 중요하게 활용할 수 있는 개념이므로 명확히 이해하고 향후 설계 시 충분하게 활용해보면 좋겠다.