25.03.17 TIL CQRS

신성훈·2025년 3월 17일

TIL

목록 보기
149/162

1. CQRS(Command Query Responsibility Segregation)란?

CQRS는 명령과 조회의 책임을 분리하는 설계 패턴이다. 전통적인 CRUD 기반의 시스템에서는 같은 데이터 모델과 저장소를 사용하여 읽기와 쓰기를 처리하지만 CQRS는 이를 별도의 모델과 서비스로 분리하여 독립적으로 운영한다.


2. CQRS 패턴의 핵심 개념

  1. Command (쓰기 작업)

    • 데이터 변경(생성, 수정, 삭제) 요청을 처리
    • 비즈니스 로직이 포함될 수 있음
    • 예: 주문 생성, 회원 정보 수정
  2. Query (읽기 작업)

    • 데이터를 조회하는 요청을 처리
    • 읽기 성능을 최적화하기 위해 캐시 또는 별도 저장소 활용 가능
    • 예: 주문 내역 조회, 사용자 정보 조회

3. CQRS 패턴의 장점

1) 성능 최적화

  • 읽기/쓰기 로직이 분리되므로 읽기 성능을 위한 최적화가 가능
  • 예를 들어, 읽기 작업은 캐시(Redis, ElasticSearch) 등을 활용하여 성능을 향상시킬 수 있음

2) 확장성 증가

  • 읽기와 쓰기가 분리되어 있어 각각 독립적으로 확장 가능
  • 예: 트래픽이 많은 시스템에서 읽기 전용 데이터베이스를 여러 개 운영 가능

3) 유지보수성 향상

  • 명령과 조회 로직이 분리되어 코드의 가독성과 유지보수성이 높아짐
  • 복잡한 비즈니스 로직을 관리하기 용이

4. CQRS 패턴의 단점

1) 복잡성 증가

  • 단순 CRUD보다 아키텍처가 복잡해짐
  • 유지보수를 위해 개발자가 CQRS 개념을 충분히 이해해야 함

2) 데이터 일관성 문제 발생 가능

  • Command와 Query 모델이 분리되어 있기 때문에 즉각적인 데이터 일관성(Strong Consistency) 유지가 어려움
  • 일반적으로 eventual consistency(최종적 일관성) 모델을 사용하여 해결

5. CQRS 적용 예제

- Command (쓰기) 구현

@RestController
@RequestMapping("/orders")
public class OrderCommandController {
    private final OrderService orderService;
    
    public OrderCommandController(OrderService orderService) {
        this.orderService = orderService;
    }
    
    @PostMapping
    public ResponseEntity<String> createOrder(@RequestBody OrderRequest request) {
        orderService.createOrder(request);
        return ResponseEntity.ok("Order Created");
    }
}
@Service
public class OrderService {
    private final OrderRepository orderRepository;
    
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    
    @Transactional
    public void createOrder(OrderRequest request) {
        Order order = new Order(request.getUserId(), request.getProductId());
        orderRepository.save(order);
    }
}

- Query (읽기) 구현

@RestController
@RequestMapping("/orders")
public class OrderQueryController {
    private final OrderQueryService orderQueryService;
    
    public OrderQueryController(OrderQueryService orderQueryService) {
        this.orderQueryService = orderQueryService;
    }
    
    @GetMapping("/{orderId}")
    public ResponseEntity<OrderResponse> getOrder(@PathVariable Long orderId) {
        OrderResponse order = orderQueryService.getOrderById(orderId);
        return ResponseEntity.ok(order);
    }
}
@Service
public class OrderQueryService {
    private final OrderRepository orderRepository;
    
    public OrderQueryService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    
    public OrderResponse getOrderById(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new RuntimeException("Order not found"));
        return new OrderResponse(order.getId(), order.getUserId(), order.getProductId());
    }
}

6. CQRS와 Event Sourcing

CQRS는 종종 이벤트 소싱(Event Sourcing)과 함께 사용됨.

Event Sourcing이란?

  • 데이터 변경을 상태(State)가 아닌 이벤트(Event) 단위로 저장하는 방식
  • 데이터 변경 이력을 추적 가능
  • 트랜잭션을 효율적으로 관리할 수 있음

CQRS + Event Sourcing을 활용하면?

  • 변경 이력을 유지하면서 다양한 방식으로 데이터 조회 가능
  • 데이터 정합성을 보장할 수 있음

7. 마무리

CQRS는 대규모 시스템에서 성능 최적화와 확장성을 고려할 때 유용한 패턴이다. 하지만 단순한 CRUD 애플리케이션에서는 과도한 설계가 될 수도 있으므로 신중하게 적용해야 한다.
데이터 일관성을 유지하기 위해 Eventual Consistency를 이해하고 적절한 이벤트 처리 전략을 마련하는 것이 중요하다는 점을 다시 한 번 깨달았다.

profile
조급해하지 말고, 흐름을 만들고, 기록하면서 쌓아가자.

0개의 댓글