CQRS는 무엇인가요? (Command Query Responsibility Segregation)

Jul 16, 2023

What is CQRS

    다음과 같은 상황을 가정해봅시다.

    • 당신은 개발자 포트폴리오 사이트 devfolio를 만들고 있습니다.
    • devfolio에는 개발자들이 자신들의 깃헙, 링크드인의 계정을 연결하고, 해당 계정의 정보를 들고 올 수 있습니다.
    • 두 사이트 모두 계정 이름, 프로필 사진, 소개글, 등록한 웹사이트 등의 정보를 가지고 있습니다.

    웹 개발자라면 social_profile_info라는 개념적 데이터 모델을 만들고, 이를 CRUD 할 수 있는 API를 만들어서 사용할 것입니다.

    CRUD Example

    Github에서 제공해주는 API에서의 변수와 Linkedin에서 제공해주는 API에서의 변수가 다를 수 있습니다. (실제 API와는 전혀 다릅니다.) 제품을 출시할 당시에는 위의 그림과 같이 응답이 왔고, 이를 받은 뒤 서버에서 변수명을 변환한 뒤 정보를 저장했습니다.

    그런데 어느 날,

    • Github에서 제공해주는 API의 변수명이 바뀌었습니다.

    그래서 Github에서 제공해주는 API를 사용하는 devfolio의 서버에서는 오류가 발생했습니다.

    CRUD Error about Github API

    서버의 코드를 수정하고 다시 배포합니다.
    사이트 배포 뒤 반응이 좋아 이번에는 Github, Linkedin뿐만 아니라

    • Discord, Slack, Rocket Punch 등의 계정도 연결할 수 있게 되었습니다.

    API가 수정되는 일이 점점 더 빈번해지고, 변경되는 시점마다 해당 기능이 동작하지 않고 심하게는 사이트가 다운되는 일이 발생합니다. 당신은 이 문제를 해결하기 위해 다음과 같은 방법을 생각해냅니다.

    • 각 계정의 정보를 업데이트해주는건 응답된 JSON 파일 그대로 업데이트하고, 이후에 웹사이트에서 보여줄 때는 필요한 정보만 추출해서 보여주면 어떨까?

    다음과 같이 데이터베이스 업데이트와 읽기의 책임을 분리하기 위해 나온 것이 CQRS입니다.

    CQRS(Command Query Responsibility Segregation)는 CUD(Create, Update, Delete) 로직을 처리하는 Command와 Read 로직을 처리하는 Query를 분리하는 패턴입니다.

    위의 예시에서는 데이터베이스에 각 계정 정보를 업데이트한다라는 Command서비스에서 각 계정 정보를 보여준다라는 Query를 분리하는 것으로 예시를 들 수 있습니다.

    예시에는 같은 데이터베이스에서 쓰기와 읽기의 데이터 모델을 분리하는 식으로 구현했지만, 아예 데이터베이스 자체를 분리하는 방법도 있습니다. 예를 들어 쓰기 전용 데이터베이스는 MySQL을 사용하고, 읽기 전용 데이터베이스는 Elasticsearch를 사용하는 방법도 있습니다.

    CRUD Error about Github API

    이러한 경우에는 CommandQuery의 데이터베이스가 다르기 때문에, 이 둘의 일관성(데이터 동기화)을 유지하기 위해 Event Sourcing을 사용하여 그 둘을 동기화시켜 주기도 합니다.

    Event SourcingCommand가 들어올 때마다 Event를 발생시켜 해당 EventQuery가 읽어서 데이터를 업데이트하는 방식입니다. 보통 해당 이벤트를 큐에다 저장해두고 이를 Query가 읽어서 데이터를 업데이트하는 public/subscribe 패턴을 사용합니다.

    Pub/Sub 패턴에 대한 정보는 웹소켓 관련 글에서 다룬 바 있습니다. :)

    CQRS는 다음과 같은 장점을 가지고 있습니다.

    • CommandQuery를 분리함으로써, Command의 로직이 변경되어도 Query의 로직에는 영향을 주지 않습니다.
    • 위의 사례처럼 여러 타입의 쓰기가 들어와도 Command의 로직을 변경하지 않아도 되어 유연하게 확장이 가능합니다.
    • CommandQuery의 로직이 분리되어 있기 때문에, 각각의 로직을 최적화하기도 쉽습니다.

    CQRS는 다음과 같은 단점을 가지고 있습니다.

    • CommandQuery의 로직이 분리되어 있기 때문에, CRUD에 최적화된 ORM을 사용할 때는 오히려 코드를 추가하여 복잡성이 증가합니다.
    • Event Sourcing을 비동기로 구현하는 경우 Command에서 업데이트한 데이터가 Query에서 바로 반영되지 않습니다. (일정 시간이 지나야 반영됩니다.)

    위와 같은 장단점때문에 CQRS는 모든 서비스에 적용하기에는 적합하지 않습니다. 하지만 특정 서비스에서는 CQRS를 적용하는 것이 복잡도를 훨씬 낮춰줄 수 있습니다. CQRS를 적용해야 하는 서비스의 예시는 다음과 같습니다.

    • 읽기 수가 쓰기 수보다 훨씬 많아서 읽기 로직을 최적화 해야하는 경우 둘을 분리할 수 있습니다.
    • 쓰기에 대한 비즈니스 규칙이 자주 바뀌는 경우 CQRS를 사용해서 복잡성을 줄여줄 수 있습니다.
    • Microservice Architecture에서는 CQRS를 사용해서 각 서비스의 데이터 모델을 분리할 수 있습니다.