CQRS 패턴이란?

초이지수·2023년 11월 27일
0

Node.js

목록 보기
7/7

🤔 CQRS가 뭐지...?

회사 재정 상... 외주 작업을 받게되었다.

외주 코드 및 AWS 계정을 처음 받아 보았을 때는.. 충격적이고... 왜 이렇게 만들었을까? 궁금한 점이 많이 생겼었다. 아직 배포 전인.. 사이트의 인스턴스가 10개였고 Nest.js로 작성한 코드의 구조가 독특해 의도가 궁금했습니다 그 중에서 CQRS 패턴을 왜 사용하셨는지 궁금해졌다.

처음에 코드를 받아 열어보았을 때, query가 따로 분리되어 있는 폴더가 있었는데... CQRS 패턴이라는 걸 알게 되었다.

외주 작업을 넘겨 받으면서, 코드와 인프라 관련된 아무런 인수인계를 받지 못해서 왜 CQRS 패턴을 사용했는지 알수가 없었고, 퇴사하셨다고.. 혼자 CQRS에 대해 공부해보면서 왜 사용했는지 추측해보기로 했다!


📌 1. CQRS 패턴이란?

CQRS는 Command and Query Responsibility Segregation의 약자로 데이터 저장소에 대한 읽기와 업데이트(쓰기) 작업을 구분하는 패턴 이다.

애플리케이션에서 CQRS 패턴으로 구현하면 성능, 확장성 및 보안을 최대화할 수 있고, CQRS로 마이그레이션하면 유연성이 생기므로 시스템이 점점 진화하고 업데이트 명령이 도메인 수준에서 병합, 충돌을 일으키지 않도록 할 수 있다고 한다.

아직은 감이 안 잡힌다.. 어떤 구조로 작성하는 패턴인지 알아보자


📎 1-1. 명령 모델 & 쿼리 모델

CQRS는 크게 명령 모델과 쿼리 모델로 나뉜다!

1. 명령 모델(Command Model)
데이터를 변경하는 작업(쓰기 작업)을 처리 한다. 이 모델은 특정 명령을 받아서 비즈니스 로직과 유효성 검사를 수행하고, 데이터를 변경한다.

2. 쿼리 모델(Query Model)
데이터를 조회하는 작업(읽기 작업)을 처리 한다. 이 모델은 최적화된 읽기 작업을 위해 종종 데이터를 별도의 형태로 저장한다.

간단하게 정리하자면,
명령 모델은 일반적으로 서비스 로직과 관련이 있고, 데이터 생성, 수정, 삭제와 같은 작업을 담당하고
쿼리 모델은 데이터베이스나 다른 저장소에서 데이터를 읽고 결과를 반환하는 역할을 담당한다.


📎 1-2. CQRS를 3단계로 적용하는 방법

1단계 : 명령과 쿼리의 개념적 분리
단일 Data Store에 Command와 Query Mode를 단일 애플리케이션 내에서 분리된 계층으로 나누는 방식

2단계 : 물리적 분리 및 도메인 모델 적용
단일 애플리케이션과 명령용 데이터베이스와 쿼리용(조회) 데티어베이스를 분리하고 별도의 Broker를 통해서 이중화 된 데이터베이스를 동기화 하는 방식.
명령 모델에 도메인 주도 설계(DDD)의 개념을 적용하여 복잡한 비즈니스 로직과 상태 변경을 관리한다.

3단계 : 이벤트 소싱 및 비동기 통합
이벤트 소싱을 통해 시스템의 상태 변경을 이벤트로 기록하고 이를 통해 시스템의 상태를 재구성한다. (이벤트 소싱 : 애플리케이션 내의 모든 처리 내용을 이벤트로 전환해서 이벤트 스트림을 별도의 DB에 저장하는 방식)
명령과 쿼리 사이의 데이터 동기화를 비동기적으로 처리하여, 시스템의 부하를 분산시키고 성능을 향상시킨다.


🤔❓ 이렇게 명령 모델과 쿼리 모델로 나뉘어 있으면, 어떤 장점이 있을까?


📌 2. CQRS 패턴을 사용했을 때 장점

1. 명확한 책임 분리
읽기와 쓰기 작업이 명확히 분리되어 있어, 각각에 최적화된 설계와 구현이 가능하고, 관리하기 쉽다.

2. 확장성
읽기 작업이 많은 애플리케이션에서는 읽기 모델을 분산 시스템이나 별도의 데이터베이스에 배치하여 확장성을 높일 수 있다.
다른 요구사항에 맞춰 읽기와 쓰기 모델을 독립적으로 조정하고 확장할 수 있다.

3. 성능 최적화 및 관리
읽기와 쓰기 작업이 분리되어 있기 때문에, 시스템은 더 높은 성능과 확장성을 달성할 수 있다. 성능을 위해 쓰기는 RDBMS로, 읽기는 Redis를 사용하는 경우 더욱 유용하다.
쿼리 작업이 병목이 되는 경우, CQRS를 통해 읽기 모델을 별도로 관리하고 성능을 향상시킬 수 있다! 캐싱, 인덱싱, 조회 최적화 등의 기법을 읽기 모델에만 집중적으로 적용할 수 있다.


🤔❓ 반대로 단점은 없을까?


📌 3. CQRS 패턴을 사용할 때 유의해야할 점

1. 복잡성 증가
명령 모델과 쿼리 모델 간의 데이터 일관성을 유지하기 위한 추가적인 메커니즘이 필요하다.
시스템 전체가 아닌 DDD(Domain-Driven Design, 도메인 주도 설계) 에서 말하는 bounded context 내에서만 사용해야 한다고 한다... 이건 아직 제대로 이해를 못했다.. 도메인 주도 설계도 공부해보자...

2. 데이터 동기화 문제
쓰기 작업 후 읽기 모델의 데이터를 갱신하는 과정에서 동기화 문제가 발생할 수 있다.

3. 학습 곡선
전통적인 CRUD 모델에 비해 학습 및 구현이 복잡할 수 있다.


💡 왜 CQRS를 사용했을까?

CQRS의 특징과 장단점을 알고나니, 왜 CQRS를 사용하려고 하셨는지 이해가 되기 시작했다.

내가 이어받게 된 외주 작업은, AI 음원을 들어보고 마음에 드는 음원은 저장 및 다운로드를 할 수 있는 기능이 메인이다. 음원 관련한 읽기 작업이 많았고, CQRS로 읽기와 쓰기 작업을 분리해 쿼리 모델에 집중해서 코드 작업을 하려고 하셨구나(나중에 확장성도 고려하신 것 같다)... 추측할 수 있었다.

아쉬웠던 점은..
쿼리 모델과 명령 모델이 명확하게 분리된 코드가 아니었다는 점이었다...
명령 모델로 작성되어야 했던 코드(데이터베이스 읽기, 생성, 수정, 삭제 관련)가 전통적인 CRUD 모델로 구현이 되어 있었다.

controller.ts, service.ts 파일에 CQRS 패턴을 적용하지 않고 명령 핸들러 없이 데이터 생성, 수정, 삭제 작업을 수행하는 코드로 작성 되어있었는데, 이는 CQRS 원칙에 어긋난다.
또한, 전통적인 CRUD 모델로 구현한 코드도 controller.ts 파일에 서비스 관련된 코드가 작성되어 있는 곳도 있었고 service.ts 파일에 데이터베이스 생성, 수정, 삭제 관련 코드가 작성되어 있는 곳도 있었다.. 그래서 CQRS 패턴을 몰랐던 나는... CQRS 패턴이라고 인식하지 못했다

그리고.. CQRS 1단계를 적용하셨고.. 이제 스타트업에서 시작하는 프로젝트에서 CQRS 패턴 적용이 효과적이었을까... 의문도 생겼다...

CQRS패턴을 적용할 때는 시스템의 명령(쓰기) 작업과 쿼리(읽기)작업을 명확하게 분리하는 것이 매우! 중요하다고 느끼게 되었고! 명확하게 분리하지 않으면... 다른 사람이 코드를 읽을 때 코드의 구조를 파악하기가 너무 어렵다.
CQRS 패턴을 적용하지 않더라도 코드를 작성할 때 통일된 구조와 로직으로 코드를 작성하는 것이 매.우. 중요. 하다.는 것을 제대로... 느꼈다.

또한, 스스로 작성하는 코드에 대해서도 반성하게 되었다. 나 또한,.. 나만 알아볼 수 있는 구조로 코드를 작성하고 있던게 아닐까? 다른 사람이 한 눈에 파악하기 쉬운 구조는 어떤 구조일까? CQRS 패턴 말고도 다양한 패턴들이 있을 텐데, 각각의 장단점은 무엇일까? 궁금하기도 했다.


읽기 작업이 많은 애플리케이션이나 대규모 시스템, 복잡한 비즈니스 로직, 높은 성능 요구사항이 있는 시스템에서 CQRS를 적용하면 성능, 확장성 및 유지보수 측면에서 이점이 있을 수 있지만, 복잡성과 구현 비용 등을 고려해 신중하게 결정해야한다.

처음에 CQRS 패턴을 적용했다가 나중에 기능적인 복잡성으로 인해 전통적인 CRUD 방식을 추가하게 된다면... CQRS 패턴을 적용하지 않는 코드가 더 좋은 코드가 될 것 같다.

CQRS 패턴은 간단한 어플리케이션에는 과도한 설계가 될 수 있으므로, 시스템의 요구사항과 복잡성을 고려하여 적절히 선택하는 것이 중요하다.... 선택은 항상 어렵다... 그러니까 더 나은 선택을 하기 위해 더욱 공부하자...


참고했던 사이트

https://terrys-tech-log.tistory.com/48

profile
닫혀 있어서 벽인 줄 알고 있지만, 사실은 문이다.

0개의 댓글