10/28

졸용·2025년 10월 28일

TIL

목록 보기
102/144

🔹 CQRS란?

CQRS는 Command Query Responsibility Segregation의 약자로,

“명령(Command)”과 “조회(Query)”의 책임을 분리하는 아키텍처 패턴을 말한다.


🔸 핵심 개념

일반적인 시스템에서는 하나의 서비스나 레포지토리가

  • 데이터를 변경(쓰기)하는 로직(save, update, delete)과
  • 데이터를 조회(읽기)하는 로직(find, getList)
    같은 모델과 같은 계층에서 처리한다.

그러나 CQRS는 이 두 가지를 분리하는 것이다.

구분CommandQuery
목적데이터 변경데이터 조회
예시회원가입, 주문 생성, 결제 취소회원 상세 조회, 주문 목록 조회
트랜잭션강한 일관성 (ACID)약한 일관성 (Eventually Consistent)
데이터베이스보통 RDB (쓰기 최적화)조회 전용 DB (읽기 최적화, 캐시/NoSQL 등)
설계 패턴DDD의 CommandHandlerDDD의 QueryService 또는 ReadModel

🔸 구조 예시 (Spring 기반)

📦 com.example.order
 ┣ 📂 command
 ┃ ┣ 📂 application
 ┃ ┃ ┗ 📜 OrderCommandService.java
 ┃ ┣ 📂 domain
 ┃ ┃ ┗ 📜 Order.java
 ┃ ┣ 📂 infrastructure
 ┃ ┃ ┗ 📜 OrderCommandRepository.java
 ┗ 📂 query
   ┣ 📂 application
   ┃ ┗ 📜 OrderQueryService.java
   ┣ 📂 dto
   ┃ ┗ 📜 OrderDetailResponse.java
   ┣ 📂 infrastructure
   ┃ ┗ 📜 OrderQueryRepository.java
  • OrderCommandService: 주문 생성/취소/수정 등 "쓰기" 담당
  • OrderQueryService: 주문 목록, 주문 상세 조회 등 "읽기" 담당
  • 이 둘은 같은 Order 엔티티를 직접 공유하지 않을 수도 있다.
    (조회용 DTO나 View 전용 테이블을 사용)

🔸 CQRS 방식 vs 전통 방식

항목CQRS 방식전통적 방식
읽기/쓰기 로직분리됨통합됨
모델 구조Command Model / Query Model단일 Model
성능고성능 확장 가능전체 로직이 얽힘
일관성최종 일관성 (Eventual Consistency)즉시 일관성
복잡도높음낮음


🔹 ✅ CQRS의 장점

장점설명
확장성 향상읽기/쓰기를 각각 다른 서버로 확장 가능 (읽기 트래픽이 많을 때 유리)
성능 최적화Query 쿼리를 캐시, Redis, Elasticsearch 등으로 별도 최적화 가능
명확한 책임 분리코드 구조가 명확해지고, 도메인 로직이 깔끔해짐
DDD와 궁합 좋음CommandHandler, Aggregate Root, Event 등을 명확히 구분 가능

🔹 ❌ 단점 및 주의점

단점설명
구조 복잡도 증가Command/Query 계층을 따로 관리해야 함
데이터 일관성 관리쓰기 후 읽기 모델로 복제 시 지연 발생 (Eventual Consistency)
학습 곡선단순 CRUD 애플리케이션엔 과한 설계일 수 있음


🔹 실제 사용 예시

  • 대규모 트래픽 서비스

    • 예) 결제 시스템, 주문 시스템, 재고 관리, SNS 피드 등
      → 쓰기보다 읽기 비율이 훨씬 높은 경우.
  • DDD + 이벤트 기반 시스템

    • Command에서 도메인 이벤트 발행 →
      별도 Event Handler가 Query 모델 업데이트

🔸 코드 예시

// Command Service (쓰기)
@Service
@RequiredArgsConstructor
public class OrderCommandService {
    private final OrderRepository orderRepository;

    public UUID createOrder(CreateOrderRequest request) {
        Order order = new Order(request.getUserId(), request.getItems());
        return orderRepository.save(order).getId();
    }
}

// Query Service (읽기)
@Service
@RequiredArgsConstructor
public class OrderQueryService {
    private final OrderReadRepository orderReadRepository;

    @Transactional(readOnly = true)
    public OrderDetailResponse getOrder(UUID orderId) {
        return orderReadRepository.findOrderDetail(orderId);
    }
}
profile
꾸준한 공부만이 답이다

0개의 댓글