CQRS 패턴이란...?

Hansu Kim·2022년 2월 18일
2

Design Pattern

목록 보기
1/1

CQRS 패턴이란

CQRS : Command and Query Responsibility Segregation(명령과 조회의 책임 분리)
즉, CQRS 패턴은 커맨드와 책임을 분리하는 패턴이다.
책임 분리를 위해서는 코드의 모듈이 분리되어야 한다.

기존 아키텍처의 데이터 접근 방식의 단점


일반적인 아키텍처에서는 비지니스로직이 데이터액세스 클래스를 호출하여 DB에 접근한다.
하지만 아래와 같은 단점이 발생할 수 있다.
아래 단점들은 Microsoft Azure의 CQRS 패턴에서 참고했으며, 한글 번역본의 경우 번역이 이상하게 되어있기에 영어로 옮겨오게 됐다.

  • There is often a mismatch between the read and write representations of the data, such as additional columns or properties that must be updated correctly even though they aren't required as part of an operation.
    • 나의 해석: 데이터 액세스 클래스의 읽기와 쓰기 함수가 같은 데이터에 대해 수행되더라도, 실제 접근하는 데이터는 상이할 수 있다. 로직에 따라 함께 업데이트되어야하는 추가적인 컬럼이나 속성들이 있을 수 있기 때문이다.
  • Data contention can occur when operations are performed in parallel on the same set of data.
    • 나의 해석: 잘못된 설계로 생기는 클래스 공유 변수나, DB의 트랜잭션 격리로 인해 쓰레드에서 데이터 경합이 발생할 수 있다는 말로 보인다.
      비지니스 로직에 따라 데이터에 접근할 경우, DB 입장에선 하나의 트랜잭션 형태로 수행될 것이며, 해당 트랜잭션의 쿼리들이 Select문이 100개, Update문이 1개라도 해당 트랜잭션언 R/W Transaction으로 처리되기 때문이다.
  • The traditional approach can have a negative effect on performance due to load on the data store and data access layer, and the complexity of queries required to retrieve information.
    • 나의 해석: 기존 방식은 데이터 액세스 클래스의 부하와 정보 검색에 필요한 쿼리의 복잡성으로 성능에 부정적일 수 있다.
  • Managing security and permissions can become complex, because each entity is subject to both read and write operations, which might expose data in the wrong context.
    • 각 엔티티가 읽기와 쓰기 작업을 동시에 수행해야하기 때문에 보안 및 권한 관리가 복잡해질 수 있으며, 잘못된 컨텍스트에서 정보가 노출될 수 있다.

CQRS 패턴에서의 데이터 접근 구조

CQRS 패턴은 단적으로 Read와 Write를 분리하는 것을 뜻한다. 하지만 그 구현은 구체적으로 어플리케이션까지만 적용할 수도 있고, DB의 모델까지만 분리할 수도 있고, DB 그 자체를 분리하여 적용할 수도 있다.
다만 DB의 모델 이상으로 Read와 Write가 분리된다면, ORM의 스캐폴딩 매커니즘으로 생성되는 SQL문들을 사용할 수 없다.

객체 지향적인 관점에서라면 데이터라는 하나의 객체를 대상으로 읽기/쓰기 등의 행위들이 구현되어야하겠지만, CQRS는 데이터 액세스 클래스를 아래와 같이 설계한다.

CQRS 패턴으로 설계된 아키텍쳐 (하나의 DB 내에 읽기/쓰기 모델 분리)

  • 명령은 데이터 중심이 아니라 작업 기반이어야 합니다.
    ("예약 상태를 예약으로 설정"이 아니라 "호텔 객실 예약").
    • 나의 의견: 작업 단위로 DB에 액세스해야 로직을 RO/RW 트랜잭션으로 효율적으로 나눌 수 있음.
  • 명령은 동기적으로 처리되지 않고 비동기 처리를 위해 큐에 배치될 수 있다.
    • 나의 의견: Spring은 Request per thread이므로, Request를 통해 명령이 수행되도록 구현하면 될 것으로 보인다.
  • 쿼리는 데이터베이스를 수정하지 않습니다. 쿼리는 도메인 지식을 캡슐화하지 않는 DTO를 반환합니다.
    • 나의 의견: Read 모델에 대한 접근이 발생할 때만 Application에 결과값이 반환되도록 설계하며, 결과값이 리턴되면, 그 결과값은 하나의 클래스이되 getter와 setter만 가지고 별도 로직을 가지지 않는 Object(클래스..?)로 처리하면 될 것으로 보인다.
      JPA의 DTO 반환을 뜻한다.
      (JPA에 대해 스터디 진행이 아직 되지 않아서 잘못 이해한 사항.)

별도의 읽기/쓰기 DB를 사용할 때의 동기화 문제

읽기 DB와 쓰기 DB를 분리할 경우, 동기화 상태 유지에 대한 난관을 겪게 된다.
일반적으로 동기화는 쓰기 DB에서 업데이트가 발생할 경우, 이벤트를 게시하는 이벤트 기반 아키텍쳐 스타일로 구현되어 처리된다. 이 경우, DB 업데이트와 이벤트 게시는 하나의 트랜잭션에서 발생해야 한다.


위 이미지에서 Read data store는 Write data store의 Read-only replica일 수도 있고, 완전히 다르게 취급되는 하나의 별도 모델일 수 있다.
만약 Read-only Replica를 여러개 사용하면 쿼리 퍼포먼스를 향상시킬 수 있다.

CQRS의 일부 구현은 Event Sourcing Pattern을 사용한다.
하지만 이벤트 소싱은 디자인에 복잡성을 더하게 된다.

Event Sourcing Pattern: https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing

결론 - CQRS의 이점 및 고려사항

이점

  • Independent Scaling
    • 읽기 모델과 쓰기 모델을 필요에 따라 독립적으로 확장 가능
  • Optimized data schemas
    • 읽기 모델은 쿼리에 최적화된 스키마를 사용 가능
  • Security
    • 호출되는 도메인 엔티티에 대해 확인하는 로직 구현이 더 쉬움
  • Seperation of concerns
    • 보통 복잡한 비지니스 로직 구현은 대부분 쓰기 모델에 속하며, 읽기 모델은 간단하게 구현된다. 그에 따라 읽기와 쓰기를 분리하면 유지관리가 더 쉽고 유연한 모델이 구현될 수 있다.
  • Simpler queries
    • DB에 논리적인 View가 아닌, Materialized View를 저장함으로써 애플리케이션에서 복잡한 조인이 사용된 쿼리문을 피할 수 있다.

고려사항

  • Complexity
    • CQRS 패턴의 아이디어는 간단하나, 해당 아이디어가 이벤트 소싱 패턴까지 이어질 경우 구현이 매우 어렵다.
  • Messaging
    • CQRS 패턴에서 메시징이 필요하진 않지만, 메시징을 사용해서 명령을 처리하고 이벤트를 게시하는 것이 일반적이다. (Kafka)
      메시징을 사용하는 경우, Application은 메시지 실패 또는 중복 메시지 처리에 대한 로직을 상세하게 구현해야 한다.
  • Eventual consistency
    • 읽기 모델과 쓰기 모델이 서로 다른 DB로 분리됐을 경우, 읽기 모델은 쓰기 모델의 변경 사항이 실시간으로 반영되도록 구현해야 한다. 그리고 만약 유저에게서 과거의 데이터를 기준으로 Request가 발생했을 경우, 처리하는데에 어려움이 발생하게 된다.

참고사이트: https://docs.microsoft.com/ko-kr/azure/architecture/patterns/cqrs

0개의 댓글