본문 바로가기

Framework/CQRS

Cafe CQRS - part 1

이 시리즈는 CQRS EDUMENT 의 tutorial 을 보면서, 나름 번역과 사견을 덧붙여 작성되었습니다.

원문 링크: http://cqrs.nu/tutorial/cs/01-design


CQRS 는 Command and Query Responsibility Segregation 의 약자로서 전통적인 CRUD 관점의 아키텍처를 Command 와 Query 로 분리해서 DDD(Domain Driven Design) 관점의 개발을 이루기 위한 하나의 패턴이라고 볼 수 있습니다. 이 개념은 Martin Fowler 가 처음 제시했고, Greg Young 이 좀 더 다듬은 개념입니다.

시작하기에 앞서 이 시리즈를 번역하게 된 이유는 개인적인 기록의 이유도 있지만 이 패턴을 학습하고 연구함으로써 개인적인 프로젝트에 적용하기에 최적이라는 판단과 실제 비즈니스 애플리케이션에서 유용할 것으로 생각했고, 정리하는 과정에서 좀 더 나은 아이디어를 얻을 수 있으리라는 생각에 정리해 보았습니다.


전문 번역가는 아니기에 오역이 있을 수 있습니다. 또한, 원문을 그대로 번역하지 않고 개인적인 사견과 함께 의도적으로 누락시킨 부분도 있습니다. 좀 더 명확한 이해를 원하시면 원문 링크를 참조하시기 바랍니다. 댓글을 통한 피드백은 언제나 환영합니다.


The Domain

이 튜토리얼에서는 일반적인 까페의 도메인을 대상으로 합니다. (여기서 도메인이라는 의미는 DDD 에서 말하는 그 도메인으로서 어떤 비즈니스 로직을 가지는 단위라고 볼 수 있습니다.) 까페에서의 비즈니스 로직이란 사람들이 Cafe를 방문하게 되면, 자리에 앉아서(open table) 음료나 음식 등을 주문하고(order drinks, foods), 음료의 경우는 웨이터가 바로 준비해 주고(serve drinks) 음식의 경우는 주방장을 통해 요리되서(prepare food) 서빙하는 형태를 생각할 수 있습니다. 음식을 주문하면 주방장은 해당 음식을 요리하고 음식이 준비되면 웨이터가 서빙하는 일반적인 비즈니스 로직을 생각할 수 있을 것입니다.

 

손님(들)은 앉은 자리에서 추가적으로 음료 혹은 음식을 주문할 수 있고, 잘못 주문한 것이 있다면 수정할 수 있어야 할 것입니다. 하지만 이미 서빙이 된 음료나 음식은 수정할 수 없어야 합니다.

 

주문한 음식, 음료 등이 전부 서빙이 되면 마지막으로 손님은 계산의 과정을 통해서(pay ordered items) 해당 테이블은 종료가 될 것입니다(close table). 만약 서빙이 안된 목록이 남아 있을 경우, 해당 목록을 취소하고 계산할 수도 있어야 할것입니다.

 

Events

위의 도메인 로직에서 중요한 부분은 동사로 표현되는 부분입니다. 보통 기존의 database-centric 적인 앱(application)은 명사에 집중하고, 그 명사들을 DB의 테이블화 하는 형태로 집중하는 경향이 있습니다. 하지만 현실 상의 도메인 로직에서는 명사도 중요하지만, 동사가 중요한 경우가 더 많습니다. 즉, 행위가 도메인 로직 자체인 경우가 많습니다. 모든 비즈니스에서는 고객이 있을 것이고, 고객의 관심사는 보통 그들의 행위입니다.

 

도메인 시나리오에서는 일어나는 행위에 대해 먼저 포커스를 맞추고 그 결과로 어떤 정보가 됩니다. 도메인에서 일어나는 그 행위에 대한 결과들을 이벤트(events)라고 할 수 있습니다. 보통 그런 이벤트들은 과거 시제로 표현되는 경우가 많습니다.(예: ~를 주문했다. ~를 서빙했다, 등 ...)

 

까페 도메인에서는 다음과 같은 이벤트를 정의할 수 있습니다. (Tab = Table)

  • TabOpened
  • DrinksOrdered
  • FoodOrdered
  • DrinksCancelled
  • FoodCancelled
  • DrinksServed
  • FoodPrepared
  • FoodServed
  • TabClosed

도메인에서 필요하다고 판단되는 이벤트는 언제든지 추가할 수 있습니다. 이중에서 특히, DrinksOrdered 와 FoodOrdered 를 분리하는 이유는 도메인 로직상 다른 처리 과정이 필요하기 때문임을 주의해야 합니다. 또한, created, updated, deleted 와 같은 형태가 아닌 것을 주목할 필요가 있는데, DDD(Domain Driven Design)에서는 도메인 행위에 포커스를 둬야 합니다. 전통적인 생각에서(CRUD-think) 탈출해야 합니다. 일반적으로 복잡한 비즈니스 로직을 가지는 앱의 경우에는 전통적인 CRUD 방식의 설계는 도메인 로직이 증가하면 할 수록 복잡해지는 경향이 있습니다. 물론 복잡한 비즈니스 도메인에서는 많은 이벤트와 커맨드가 발생이 될 수 있으나 행위나 결과에 초점을 맞춤으로서 좀 더 명확하게 비즈니스 앱을 바라볼 수 있게 될것입니다.


Commands

커맨드(Command)는 해당 도메인에 대한 요청(Requests)이라고 생각하면 무리가 없을 것입니다. 이벤트가 어떠한 행위의 결과라고 볼 수 있듯이 커맨드는 승인/거절이 가능한 행위 그 자체라고 볼 수 있습니다. 승인된 행위 하나는 여러 개의 이벤트를 발생시키거나 단 하나만 발생시킬수도 있을것입니다. 반면에 거절된 커맨드는 예외를 발생 시키는 형태가 될것입니다.

  

이 도메인에서는 다음의 커맨드를 정의할 수 있습니다.

  • OpenTab
  • PlaceOrder
  • AmendOrder
  • MarkDrinksServed
  • MarkFoodPrepared
  • MarkFoodServed
  • CloseTab

커맨드는 현재 시제로 표현되는 것이 보통입니다. 

 

Exceptions

모델링 과정에서 중요한 부분 중 하나는 거절된 커맨드가 초래하는 결과인데, 도메인 로직에서 발생한 예의의 경우 UI frontend 쪽으로 무엇이 잘못 되었는지 알려줄 필요가 있을 수 있습니다. (개발적인 관점에서 조금 더 생각해보면 예외를 던지지 않게될 경우, UI 측에서 도메인측에게 상태를 재조회하게 되거나 UI 쪽에서 도메인의 상태를 추측하게 만들 수 있으므로 예외를 던지는 것이 더 나은 판단이 될 수 있습니다.)

 

세가지 정도의 예외를 정의할 수 있는데,

  • CannotCancelServedItem
  • TabHasUnservedItems
  • MustPayEnough

이름에서 보는 것 처럼 커맨드가 무엇이 잘못됐는지를 명확하게 설명이 가능합니다.(이런식으로 굳이 코드에 주석을 추가하지 않고 코드 자체가 명확하게 로직을 설명하는 것이 더 현명할 수 있습니다.)

 

Aggregates

물론, 동사로만 설계가 가능하지 않습니다. 어느 순간이 되면 명사가 필요하게 되는데, 요청한 커맨드에 대한 승인/거절을 결정하기 위해 nouns(=state), 즉, 명사가 필요합니다. 예를 들어 서빙된 음식을 취소하려고 할 경우 해당 커맨드를 거절하기 위해서는 어떤 음식을 서빙했는지에 대한 상태를 알아야 하는 것입니다.

 

도메인에게 필요한 이러한 모든 정보는 과거에 발생된 events stream 으로부터 알수 있는데, 일반적으로 해당 도메인의 모든 events stream 이 필요하지는 않습니다. 여기서는 각각의 테이블(Tab)에 대한 stream 이면 충분합니다. 그러므로, 각 Tab에서는 각각의 events stream이 필요하다는 것을 알 수 있습니다.

 

이 부분에서 필요한 것이 Aggregate 입니다. (DDD에서 애기하는 그 Aggregate 입니다.) 각 Aggregate 는 각자의 events stream 을 가지고 있게 될 것이고 그것을 통해서 각자의 state 관리가 가능해 질 것입니다. 각각의 Aggregate 는 완전히 독립적이어야 하므로 Aggregate 에 대한 Id를 가지고 있을 것이고, 각 Aggregate는 해당 Tab 의 모든 과거 이벤트를 가지고 있을 것이므로, 어떤 커맨드를 허용해야하는지를 판단할 수 있을 것입니다.

 

좀더 명확하게 Aggregate는 다른 어떤 것들을 참조하지 않는 단일 오브젝트이고(A single object, which doesn't reference any others.) 하나의 Aggregate에서 파생된 독자적인 오브젝트 맵을 가지고 있으며, 외부에서는 오직 해당 Aggregate만 알면 되는 것입니다.(An isolated graph of objects, with one object designated as the root of the aggregate. The outside world should only know about the root.)

여기서는 단순히 Tab(table)에 Aggregate를 정의하지만, 실 비즈니스 앱에서는 Aggregate를 정의하는 것 자체가 굉장히 중요하며 많은 노력이 요구됩니다.



'Framework > CQRS' 카테고리의 다른 글

Event Sourcing  (0) 2015.12.15
CQRS + Event Sourcing – A Step by Step Overview  (0) 2015.12.15
Cafe CQRS (Read Models) - part 4  (0) 2015.12.10
Cafe CQRS (Domain Logic) - part 3  (0) 2015.12.01
Cafe CQRS - part 2  (0) 2015.12.01