C# 아키텍처 마스터리 — ASP.NET Core에서의 CQRS (도움이 될 때, 해가 될 때) (파트 9)
Source: Dev.to
Introduction
CQRS는 현대 .NET에서 가장 오해받기 쉬운 패턴 중 하나입니다.
일부 팀은 너무 일찍 도입하고, 다른 팀은 두려움 때문에 완전히 회피합니다. 두 실수 모두 CQRS를 프레임워크 선택이 아니라 아키텍처 전략으로 다루지 못한 데서 비롯됩니다.
이번 Part 9에서는 CQRS가 실제로 무엇인지, 언제 명확성을 제공하고 언제 ASP.NET Core 시스템에 해를 끼치는지 설명합니다.
What CQRS Stands For
Command Query Responsibility Segregation
- Commands는 상태를 변경합니다.
- Queries는 상태를 읽습니다.
이 두 책임이 분리됩니다.
What CQRS Does Not Require
- 이벤트 소싱
- 마이크로서비스
- 메시지 브로커
- 별도 데이터베이스
이들은 선택 사항이며, CQRS의 핵심은 아닙니다.
Traditional CRUD Services
CRUD 서비스는 일반적으로 다음을 혼합합니다:
- 읽기
- 쓰기
- 검증
- 비즈니스 규칙
- 매핑 로직
하나의 추상화에 담아냅니다.
// ❌ CRUD service
class OrderService
{
Order Get(int id) { }
void Create(Order order) { }
void Update(Order order) { }
}
시스템이 성장함에 따라 이는 다음과 같은 문제를 야기합니다:
- 이해하기 어려움
- 최적화가 어려움
- 독립적인 확장이 어려움
CQRS in Clean Architecture
- Commands는 Application 레이어에 존재합니다.
- Queries는 Application 레이어에 존재합니다.
- 인프라스트럭처가 영속성을 구현합니다.
- ASP.NET Core는 HTTP를 명령/쿼리로 매핑합니다.
경계가 이미 존재하는 경우 CQRS는 자연스럽게 맞아떨어집니다.
Commands
Commands는 상태를 변경하려는 의도를 나타냅니다.
public record CreateOrderCommand(decimal Total);
class CreateOrderHandler
{
public Task Handle(CreateOrderCommand command) { }
}
특징
- 최소한의 데이터(또는 아무것도) 반환
- 검증을 포함
- 비즈니스 규칙을 강제
Queries
Queries는 질문을 나타냅니다.
public record GetOrderQuery(int Id);
class GetOrderHandler
{
public Task Handle(GetOrderQuery query) { }
}
특징
- 절대 상태를 변경하지 않음
- 최적화된 읽기 모델을 사용할 수 있음
- 도메인 객체를 우회하는 경우가 많음(의도적)
When CQRS Is Beneficial
- 읽기 모델과 쓰기 모델이 크게 다를 때
- 쿼리 성능이 중요할 때
- 비즈니스 규칙이 복잡할 때
- 팀이 명확한 의도 분리를 원할 때
- Vertical Slice Architecture(VSA)를 사용 중일 때
CQRS는 중대형 시스템에서 빛을 발합니다.
When CQRS Hurts
- 시스템이 단순 CRUD 앱일 때
- 팀에 아키텍처 규율이 부족할 때
- 모든 것이 “핸들러”가 되어 버려서 → 보일러플레이트가 가치보다 많아질 때
핸들러가 급증하는 예시:
CreateUserCommandHandler
UpdateUserCommandHandler
DeleteUserCommandHandler
GetUserByIdQueryHandler
GetUsersQueryHandler
이렇게 되면 이득 없이 잡음만 늘어납니다.
CQRS vs. MediatR
많은 팀이 CQRS = MediatR이라고 착각하지만 이는 잘못된 이해입니다.
- MediatR은 메시징 라이브러리이자 편의 도구입니다.
- CQRS는 설계 결정이며 책임 분리입니다.
MediatR 없이도 CQRS를 구현할 수 있습니다.
CQRS Paired with Vertical Slice Architecture
각 슬라이스는 다음을 포함합니다:
- 명령 또는 쿼리
- 핸들러
- 검증기
- 엔드포인트
Features/
└─ Orders/
├─ Create/
└─ GetById/
이렇게 하면 결합도가 낮아지고 명확성이 향상됩니다.
Adoption Checklist
CQRS를 도입하기 전에 다음 질문을 해보세요:
- 읽기와 쓰기가 서로 다르게 진화하고 있나요?
- 성능이 실제 문제인가요?
- 도메인 복잡도가 증가하고 있나요?
- 이것이 인지 부하를 줄여줄까요?
- 팀이 필요한 규율을 갖추고 있나요?
위 질문 중 하나라도 “아니오”라면 CQRS 도입을 미루세요.
Summary
- CQRS는 만능 해결책이 아닙니다.
- 올바르게 사용하면: 의도를 명확히 하고, 사고를 단순화하며, 우아하게 확장됩니다.
- 잘못 사용하면: 절차가 늘어나 팀 속도가 느려지고, 단순한 로직이 가려집니다.
시니어 엔지니어는 언제 사용하면 안 되는지 알고 있습니다.