CQRS
정의
CQRS는 Command and Query Responsibility Segregation(명령과 조회의 책임 분리)을 나타낸다. 이름처럼 시스템에서 명령을 처리하는 책임과 조회를 처리하는 책임을 분리하는 것이 CQRS의 핵심이다. 이제 명령과 조회에 대해 정의할 필요가 있다. CQRS에서 명령은 시스템의 상태를 변경하는 작업을 의미하며 조회는 시스템의 상태를 반환하는 작업을 의미한다. 정리하면, CQRS는 시스템의 상태를 변경하는 작업과 시스템의 상태를 반환하는 작업의 책임을 분리하는 것이다.
모든 연산이 명령과 조회로 쉽게 양분되지는 않습니다. 개념적으로 어려운 경우도 있고 동시성 등 기술적인 문제도 있습니다. Martin Fowler는 스택 자료구조의 pop() 연산을 예로 들었습니다.
CQRS 오해
CQRS와 그 관련 기술들은 .NET 환경을 중심으로 발전해왔고 점차 Java, Ruby 등의 생태계로 확산되고 있다. 국내에서는 아직 크게 주목받지는 않지만 최근 CQRS에 대한 관심이 늘어나고 있다. CQRS를 처음 접하는 국내 프로그래머들은 혼란스러워하거나 오해를 하곤 한다. 비단 이런 현상은 CQRS나 국내 환경에 국한되지는 않는다. CQRS 말고도 여러 기술들이 확산되는 과정에 맹신과 괴담들이 퍼져왔다. 또한 해외 커뮤니티에서도 CQRS의 실체에 대해 혼란스러워하는 모습을 어렵지 않게 볼 수 있다. 저 또한 CQRS를 처음 공부할 당시 응용기술에 대한 이해보다 CQRS가 무엇인지에 대해 가장 많이 고민했고 몇 차례 주변에의 질문에도 명확한 답변을 얻지는 못했다.
Stereotypical 아키텍처

POST HTTP 요청을 통해 CreateUser 명령을 입력받아 도메인 계층에 전달하고 명령이 완료되면 201(Created) 응답을 반환한다. 응답에는 생성된 자원의 위치를 나타내는 location 헤더와 생성된 사용자 엔터티에 대한 본문이 포함된다. 이것은 자원을 생성하는 Web API의 흔한 동작입니다. 이 동작을 지원하기 위해 도메인 계층의 reateUserAsync() 메서드는 명령 실행 후 UserPresentation 개체를 반환한다.

CreateUserAsync() 메서드는 크게 두 가지 작업을 수행한다. 첫번째는 CreateUser 명령에 들어있는 정보를 사용해 새 사용자 엔터티를 생성하는 것이고, 두 번째는 생성된 사용자 엔터티를 조회해 표현 계층 모델로 변환해 반환하는 것이다. 영속 모델(User) 개체를 그대로 반환하면 PasswordHash 속성이 서비스 외부로 노출되는 보안 문제가 발생한다.


테스트 케이스가 검증하고자하는 내용과는 무관한 것을 알 수 있다. 테스트 케이스의 관심사는 새 엔터티가 잘 추가되는지이지 메서드가 어떤 값을 반환하는지가 아니다. 반환값에 대한 검증은 별도의 테스트 케이스가 필요하다. 하지만 위 코드가 빠지면 실제 코드에 문제가 없음에도 불구하고 NullReferenceException이 발생해 테스트 케이스가 실패한다. 저장소에 대한 테스트 대역이 FindAsync() 메서드 호출에 대해 null 참조를 반환하기 때문이다.
정리
설명과 예제 코드를 통해 CQRS의 의미와 CQRS가 그렇게 화려하고 복잡하지 않다는 것을 알게되었다. CQRS는 그 이름처럼 명령과 조회의 책임을 분리하는 것이 핵심이다. 지금 당장 기존 시스템의 일부에 CQRS를 적용해 복잡도를 낮추는 것도 어렵지 않다.
하지만 CQRS는 어려운 시나리오에서도 유용하며 흔히 다양한 기술과 조합되어 사용된다. 예를 들어, 이벤트 소싱(Event Sourcing)은 CQRS 적용이 필수라고 알려져있다.

Reference