CQRS pattern
CQRS (Command Query Responsibility Segregation) 의 핵심은 read/write 가 구분된다는 점이다.
보통 우리가 client 라고 부르는 쪽에서 필요한 데이터를 가져올때(read) 와 변경사항을 저장할 때(write) 우리는 같은 model 을 사용한다.
그런데 CQRS 는 이것을 각자 다른 model 을 사용한다. 한 예는 read때는 read.myserver.com 을 사용하고, write 때는 write.myserver.com 을 사용한다고 봐도 된다.
간략하게 어떤 개념인지를 파악하는데는 마틴파울러의 글이 좋은 듯 싶다. 글에 있는 이미지도 이해를 하는데 도움이 된다.
- 마틴파울러, CQRS
- CQRS 의 사용을 주의하라고 한다. 이것은 복잡성을 증가시킬 요인이 크다고 이야기 한다.
- 시스템의 일부에서만 사용하라고 이야기한다.
CQRS pattern - Azure Architecture Center | Microsoft Learn
CQRS pattern - Azure Architecture Center | Microsoft Learn 의 내용을 일부정리
CQRS 에선 command 는 update 할 때 query 를 read 할 때 쓰인다. 그리고 write 과 read 에 대해서 각자 다른 model들을 사용한다.
data 를 update 할 때는 command
data 를 read 할 때는 query
command 는 task based 여야 한다. data centric 이 아니라.
- 예를 들어, ’호텔객실 예약’이라고 해야지, ’ReservationStatus 를 Reserved 로 변경’이라고 하지 않는다.
- 한가지 방법은 command를 보내기 전에,
- client에서 validation rule들 실행하고,
- 버튼을 비활성화하고,
- UI 에 이유를 설명하는 것
- 예를 들면, ‘방이 남지 않았습니다.’
- 이 방법으로 server 쪽 command 의 실패의 원인은 race condition 들(2개의 유저들이 마지막 방을 예약하려고 하는것)로 좁혀질 수 있다. 심지어 이런 때도 데이터와 로직(손님을 대기목록에 넣는 것)을 추가해서 해결할 수 있다.
command들은 synchronous 하게 처리되지 않고, queue 에 넣어서 asynchronous 하게 처리할 수 있다.
Query 들은 db를 변경하지 않는다. query는 도메인 지식을 캡슐화하지 않는 DTO를 반환한다.
이렇게 write 와 read 에 대한 model 을 분리하면, 설계(design), 구현(implementation)이 간단해진다.
isolation 을 강화하기 위해 물리적으로 read용, write용으로 db를 나눠도 된다. read 의 경우 필요한 join 등을 미리 해놓은 결과를 저장해서 보여주는 것처럼 다른 schema 를 사용할 수 있다. 이것은 materialized view 를 쓰거나, NoSQL 같은 다른 type 의 db 를 쓰는 등의 방법을 사용할 수 있다.
이 경우 write 에 사용하는 db 와 read 에 사용하는 db 의 동기화가 필요한데, 이때는 대체로 write db 에 update 가 발생할 때 event 를 발생시켜서 read db 를 update 하게 된다. 다만 이때의 문제는 db에 write 가 된 이후에 event 가 성공적으로 전달되지 못하는 경우 data 의 consistency 를 보장할 수 없다.
read store 와 write store read store 는 write store 의 복제본(read-only replica)이거나, write store 와 상관없이 다른 구조를 갖고 있을 수도 있다. - 여러개의 read-only replica 를 갖고 있으면, 이 replica 가 application instance 에 가까이 있는 경우, query 성능이 향상 될 것이다. - 보통 read store 의 부하가 더 높은데, read 와 write store 를 분리하면, 각각의 부하에 맞게 적절하게 확장이 가능하다.
CQRS 일부 구현은 event sourcing pattern 을 사용한다.
- application 의 state 가 sequence of events 로 저장된다.
- 각 event 는 data에 대한 set of changes 를 갖는다.
- 그래서 이 event 들을 다시 실행하면, 현재의 state 가 되는 것이다.
- event sourcing pattern 을 사용하면 design 이 복잡해 진다.
구현상 어려운점
- 복잡성, 개념은 간단한데, application design 이 복잡하다. event sourcing pattern 이 들어가면 더 복잡하다.
- 메시징, application 은 message 의 실패, duplicated message 에 대한 처리를 해야 한다.
- Eventual consistency(최종 일관성) : read db 의 data 가 최신이 아닐 수 있다.(stale) 이렇게 stale data 를 기반으로 write 을 요청하는 경우도 있을 수 있는데, 그것을 구분하기 어렵다.
언제 CQRS pattern 을 쓰는가?
- 많은 사용자가 동일한 데이터에 동시에 액세스 하는
- 복잡한 프로세스를 안내하는 task-based UI 에서 쓸 수 있다.
- 쓰기 model 보다 read model 의 성능이 더 중요한 경우, write에 비해 더 많은 read 가 있는 경우등에 유리, write model instance 가 적으면, merge conflict 발생을 최소화할 수 있다.
- 한팀은 복잡한 domain model 과 write model 에 집중하고, 다른 한팀은 read model 과 UI 에 집중할 수 있는 상황이라면 사용할 수 있다.(me: 세부적인 내용은 다르겠지만, server side, client side 로 나눠서 개발하는 것과 비슷한 경우일듯)
- 시간이 지남에 따라 진화하고, 여러 다양한 버전의 model 을 포함할 수도 있는 시스템
- 비지니스 규칙이 정기적으로 변화하하는 시나리오
- 다른 시스템들과의 통합, 특히 event sourcing 과 결합된 경우.
- 하나의 sub system 의 일시적인 오류가 다른 sub system 으로 영향이 미치지 않아야 하는 시스템
쓰지 않는게 나은 경우
- domain 이나 business logic 이 간단한 경우
- 간단한 CRUD style 이면 충분한 경우
CQRS pattern 을 시스템의 제한된 부분(이 부분이 추후 중요해질 것이라 여겨지는)에 적용하는 것을 고려해보자.
댓글 없음:
댓글 쓰기