- 전술적 설계는 도메인 모델의 세부사항들을 그리기 위해 얇은 붓을 사용하는 것과 같다.
- 가장 중요한 도구 중 하나는
Entity``와
Value Object를 알맞은 크기의
Aggregate으로 묶는 데 사용한느
Aggregate 패턴`이다. - DDD는 도메인을 최대한 명확한 방법으로 모델링하는 거셍 관한 것이다.
- 도메인 이벤트의 사용은 명확하게 모델링하는 것을 도와주면서, 도메인에 발생한 것에 대해 알아야 하는 내용을 시스템과 공유하는 것을 돕는다.
- 공유할 대상이 로컬의 바운디드 컨텍스트일 수도, 다른 원격의 바운디드 컨텍스트일 수도 있다.
- 엔티티는 독립적인 것이다.
- 각 엔티티는 같은 형태를 띠거나 다른 형태의 엔티티들과의 특성을 구별할 수 있는 고유한 식별성을 갖는다.
- 엔티티는 변할 수도 있고, 변하지 않을 수도 있다.
- 다른 모델링 수단들과 엔티티를 구분해주는 주 요인은 유일성, 즉 그것의 독립성에 있다.
- 값 객체 또는 간단히 말해 값은 불변의 개념적 완전성을 모델링한다.
- 모델에서 값은 그야말로 값이다.
- 엔티티와 달리, 공유한 식별성이 없으며, 값 형태로 캡슐화된 속성을 비교함으로써 동일함이 결정된다.
- 그뿐만 아니라 값 객체가 어떤 것을 나타낸다기보다는 엔티티를 서술하고, 수량화하거나 측정하는데 사용된다.
-
각 애그리게잇은 1개 이상의
엔티티
로 구성되고, 그 중 한 엔티티는애그리게잇 루트
라고 부른다. 또한, 그 구성에값 객체
를 포할 수 있다. -
각 애그리게잇의 루트 엔티티는 애그리게잇 안의 다른 모든 요소를 소유한다. (루트 엔티티의 명칭은 애그리게잇의 개념적 명칭이다.)
-
애그리게잇이 모델링하는 개념적 완전성을 적절하게 표현할 수 있는 명칭으로 루트 엔티티 명칭을 정의해야 한다.
-
각 애그리게잇은 일관성 있는 트랜잭션 경계를 형성한다.
-
이것은 트랜잭션 제어가 데이터베이스에 커밋될 떄, 한 어그리게잇 내의 모든 구성 요소는 반드시 비즈니스 규칙을 따르면서, 일관성 있게 처리된다는 것을 의미한다.
- 애그리게잇 설계의 4가지 기본 규칙은 다음과 같다.
- 애그리게잇 경계 내에서 비즈니스 불변사항들을 보호하라.
- 작은 애그리게잇을 설계하라
- 오직 ID를 통해 다른 애그리게잇을 참고하라.
- 결과적 일관성을 사용해 다른 애그리게잇을 갱신하라
- 위의 규칙들을 반드시 엄격하게 지키지 않아도 된다.
- DDD를 신중하게 적용할 때, 효과적으로 동작하는 애그리게잇을 설계할 수 있도록 도와주는 가이드로 볼 수 있다.
- 규칙 1은 결과적으로 트랜잭션이 커밋될 떄 비즈니스의 일관성이 지켜지는 것에 기반을 두고, 애그리게잇 구성 요소를 결정해야 한다는 의미이다.
- 위 사례를 예시로 들어보면, Product는 트랜잭션 끝에 ProductBacklogItem 인스턴스로 구성되는 모든 것이 반드시 Product의 루트와 일관되게 처리되도록 설계한다.
- 또한, Sprint는 트랜잭션의 끝에 CommittedBacklogItem 인스턴스로 구성되는 모든 것이 반드시 Sprint 루트와 일관되게 처리되도록 설계한다.
- 이 규칙은 각 애그리게잇의 메모리 사용량과 트랜잭션 범위가 비교적 작아야 함을 강조한다.
-
위 다이어그램에 표현된 애그리게잇은 작지 않다.
-
여기서, Product는 문자 그대로 BacklogItem 인스턴스들 그리고, Sprint의 가능한 가장 큰 모음을 담고 있다.
-
일반적으로 이런 설계 방식은 매우 나쁜 선택이다.
-
왜냐하면, 이런 모음들은 시간이 지나면서 1000여 개의 BacklogItem 인스턴스와 약 100여 개의 Release, Sprint 인스턴스로 엄청나게 크게 불어날 수 있기 때문이다.
-
하지만, 아래 다이어그램처럼, Product 애그리게잇을 4개의 애그리게잇으로 구성하는 형태로 분해할 수 있다.
- 이들은 빠르게 로드되고, 더 작은 메모리를 차지하며, 가비지 컬렉션도 더 빠르다.
- 하지만, 가장 중요한 것은 이 애그리게잇들은 이전의 큰 클러스터의 Product 애그리게잇보다 훨씬 더 자주, 성공적인 트랜잭션을 수행할 것이라는 점이다.
- 이 규칙을 따르면, 연관된 각 작업이 한 명의 개발자가 관리할 수 있을만큼 작기 때문에, 각 애그리게잇이 좀 더 쉬워지는 부가적인 이득을 얻을 수 있으며, 테스트 또한 보다 더 쉬워질 것이다.
애그리게잇을 설계할 때 새겨둬야 할 사항 하나가 SRP라는 단일 책임 원칙이다.
- 만일 애그리게잇이 너무 많은 일을 한다면, 이는 SRP를 따르지 않는 것이고, 이후 애그리게잇의 크기에 대해 재논의할 가능성이 크다.
- 예를 들어, 만들고자 하는 Product가 스크럼 제품에 주안점을 두고 있는지, 아니면 다른 것도 함께 추구하는 것인지
- 즉, Product를 변경하는 이유가 더 나은 스크럼 제품을 만들기 위해서인지? 아니면 백로그 아이템, 릴리스, 스프린트를 관리하기 위해서인지? 에 대해 고민해보면, 답을 알 수 있다.
- 여기에서는 더 나은 스크럼 제품을 만들기 위해 Product를 바꿔야 한다는 목적에 초점을 둬야 한다.
- 이러한 형태는 애그리게잇을 작게 유지하고, 동일한 트랜잭션 내에 여러 애그리게잇을 수정하려는 접근을 방지해준다.
- 이렇게 식별자을 통해서만 레퍼런스를 얻는 규칙의 장점은 다음과 같다.
- 더 적은 메모리 요구와 리파지토리로부터의 빠른 로딩을 통해 애그리게잇 설계를 작고 효율적으로 유지할 수 있게 해준다.
- 동일한 트랜잭션 내에 다른 애그리게잇을 수정하지 않는 규칙이 잘 지켜지도록 해준다.
- 애그리게잇을 관계형 데이터베이스, 문서 데이터베이스, 키/밸류 리파지토리 그리고 데이터 그리드/패브릭과 같은 다른 형태의 저장 메커니즘으로도 쉽게 저장할 수 있다는 것이다.
- 이는 MySQL 관계형 테이블, PostgresSQL이나 MongoDB, GemFire/Geode, Coherence 그리고 GigaSpaces와 같은 JSON 기반의 리파지토리 사용을 선택적으로 결정할 수 있다는 것을 의미한다.
- BacklogItem은 Sprint와 연계되어 수행된다.
- 그래서, BacklogItem과 Sprint 모두 이것에 맞춰 설계가 이루어진다.
- 먼저, BacklogItem은 관여된 Sprint를 알아야 한다.
- 이는 BacklogItem의 상태가 해당 Sprint의 SprintID를 갖도록 정의하는 하나의 트랜잭션 안에서 관리된다.
결과적 일관성이 두럽게 느껴진다면
- 결과적 일관성을 사용함에 있어 엄청나게 힘든 점은 없지만, 실제 경험해보기 전까지는 결과적 일관성 사용에 대한 걱정이 있을 수도 있다.
- 그렇다고 해도 비즈니스에 의해 정의된 트랜잭션 경계에 따라 모델을 애그리게잇으로 분리시켜야 한다.
- 2개 이상의 애그리게잇을 단일한 데이터베이스 트랜잭션으로 묶어 처리하고 싶어 할 수도 있다.
- 이전에 이철머 커다란 트랜잭션을 처리했고, 성공했을 수도 있지만, 다른 모두를 위해 일관성 있게 사용할 필요가 있다.
- 이는 초기 단계부터 너무 거대한 처리를 만들지 않도록 해주는 기법이다.
- 이것이 애그리게잇을 사용하는 근본적인 이유는 아니지만, 결과적으로 트랜잭션 실패 경험을 줄여줄 수는 있을 것이다.
- DDD를 사용할 때 바운디드 컨텍스트 내의 보편언어를 모델링한다는 것을 항상 기억해야 한다.
- 따라서, Product 애그리게잇의 모든 부분은 보편언어에 따라 모델링해야 한다.
- 모든 것이 조화를 이룰 수 있도록 도메인 전문가와 개발자들 사이에 긴밀한 협업이 필요하다.
- 효과적인 소프트웨어 모델은 항상 일을 하는 비즈니스의 방식을 고려한 일련의 추상화에 기반을 두고 있다.
- 이떄 모델링하는 각 개념마다 적절한 수준의 추상화를 선택해야 한다.
- 만일, 보편언어와 관련된 가이드를 따른다면 적절한 추상화를 설정할 수 있다.
- 적어도 모델링 언어의 기반에 지식을 전달해주는 도메인 전문가가 있기 때문에, 훨씬 정확하게 추상화를 모델링할 수 있다.
- 하지만, 가끔은 잘못된 문제를 푸는 것에 지나치게 몰두한 나머지, 소프트웨어 개발자가 지나칠 정도로 추상화를 적용하기도 한다.
- 이런 잘못된 방식을 따르는 상황이 나타날지 의문을 갖는 사람들도 있겠지만, 이런 부적절한 추상화 수준은 기술적인 측면으로 구현을 생각하는 상황에서 자주 등장한다.
- 팀이 정의한 도메인 저문가의 멘탈 모델에 따라 보편언어를 모델링해야 한다.
- 비즈니스가 지금 당장 요구하는 것을 모델링하면 상당한 시간, 예산, 코드를 아끼고, 곤란한 상황에 빠지지 않을 수 있다.
- 더 나아가 정확하고 유용한 바운디드 컨텍스트에 효과적인 설계를 반영한 모델링을 통해 대단히 멋진 서비스에 더 많은 기여를 하게 될 것이다.
- 비즈니스 불변사항을 보호할 일관성 경계는 여전히 유지하면서도, 애그리게잇의 경계를 결정하고, 큰 클러스터를 설계하는 것을 방지할 수 있을지 궁금할 것이다.
- 만일, 이미 큰 클러스터의 애그리게잇을 만들었다면, 좀 더 작은 것들로 리팩토링하는 데 이 방법을 사용할 수도 있다.
- 일관성 경계 모표에 도달하는 데 도움을 줄 아래 설계 단계들을 살펴보자.
- 먼저 애그리게잇 설계의 2번재 규칙인 "작은 애그리게잇을 설계하라"에 집중하자.
- 애그리게잇 루트로 제공될 오직 1개의 엔티티만을 갖는 애그리게잇을 생성한다. (조만간 1개 이상의 엔티티를 둘 수 있지만, 그 경우에 대해서는 일단 생각하지 않는다)
- 각 엔티티들을 단일의 루트 엔티티와 관련이 가장 깊다고 생각되는 필드/속성/프로퍼티로 채우자.
- 여기서 가장 중의할 점은 애그리게잇을 식별하고 찾는 데 필요한 모든 필드/속성/프로퍼티를 정의하는 것 뿐만 아니라 애그리게잇을 초기에 만들 떄 유효한 초기 상태를 구성하는데 필요한 모든 추가적인 필드/속성/프로퍼티를 정의하는 것이다.
- 이제 애그리게잇 설계의 1번째 규칙인 "애그리게잇 경계 내의 비즈니스 불변사항을 보호하라"로 관심을 돌리자.
- 이미 이전 단계에, 단일 엔티티 애그리게잇을 저장할 때 모든 필드/속성이 반드시 최신의 정보를 포함한 상태여야 한다는 것을 알았을 것이다.
- 하지만, 지금은 애그리게잇을 한 번에 하나씩 살펴봐야 한다.
- 애그리게잇 A1을 살펴본다고 할 때, 이미 정의한 다른 애그리게잇들 중에 A1 애그리게잇이 변경될 때 함께 갱신되어야 하는 것이 있는지 도메인 전문가에게 확인한다.
- 애그리게잇의 행위에 관련된 모든 갱신에 걸리는 시간을 파악할 수 있는 관련된 각 애그리게잇의 목록과 일관성 규칙을 만든다.
- 다시 말하면, "애그리게잇 A1"을 목록의 가장 앞에 위치시키고, A1의 변경에 따라 바뀌어야 할 다른 애그리게잇들을 그 아래에 위치시킨다.
- 반응에 맞춘 갱신이 일어나는 시간은 얼마나 걸릴지 도메인 전문가에게 확인하자.
-
- 즉시 또는 2) N초/분/시간/일과 같은 2가지 유형의 명세로 정의될 것이다.
- 올바른 비즈니스 임계치를 찾는 1가지 가능한 방법은 받아들여질 수 없는 과장된 소요 시간(몇 주 또는 몇 달과 같은)을 먼저 제시하는 것이다.
- 비즈니싀 전문가로 하여금 받아들일 수 있는 소요 시간을 답하게 만들 수 있을 것이다.
- 각각의 애그리게잇들이 즉시 처리되어야 할 경우, 동일한 애그리게잇 경계 안에 그 2개의 엔티티를 구성하는 것을 긍정적으로 검토해야 한다.
- 그 의미는 예를 들어, 애그리게잇 A1과 애그리게잇 A2를 새로운 애그리게잇 A[1, 2]로 구성한다는 것이다.
- 이전에 정의했던 애그리게잇 A1과 A2는 더 이상 존재하지 않고, 오직 애그리게잇 A[1, 2]만 존재한다.
- 각각의 애그리게잇들이 주어진 시간에 따라 각각 반응하는 경우, 애그리게잇 설계의 4번째 규칙인 "결과적 일관성을 사용해 다른 애그리게잇을 갱신하라"를 사용해서 갱신한다.
- 이 그림에서 A1 관점에서 모델링을 살펴보자.
- A1의 일관성 규칙 목록에는 A2가 적혀 있고, C14는 소요 시간(30초)를 갖는다.
- 결과적으로 A1과 A2는 하나의 애그리게잇 A[1, 2] 안으로 통합 모델링된다.
- 또한, 런타임 중에 애그리게잇 A[1, 2]는 애그리게잇 C14를 갱신하도록 하는 도메인 이벤트를 발행시킨다.
- 모든 애그리게잇이 함께 즉각적인 갱신에 들어가야 한다고 비즈니스 측에서 일방적으로 주장하지는 않는지 주의를 기울여야 한다.
- 설계 회의에 참여하는 많은 사람들이 DB 설계와 데이터 모델리에 영향을 받을 때 특히 강하게 이런 경항을 보일 수 있다.
- 그 이해관계자들은 트랜잭션 위주의 관점을 가질 것이다.
- 하지만, 실제로 비즈니스가 모든 상황에 즉각적인 일관성을 요구할 가능성은 매우 낮다.
- 이런 생각을 바꾸기 위해 현재의 큰 클러스터 애그리게잇을 구성하는 여러 애그리게잇들에 걸친, 다수의 사용자들에 의해 동시에 발생하는 갱신들로 인해 어떻게 트랜잭션이 실패하게 될지 입증하는 데 시간을 보내는 상황이 발생할 수도 있다.
- 그뿐만 아니라 그런 큰 클러스터 설계로 인해 얼마나 많은 메모리 오버헤드가 발생하는지에 대해 이야기를 해야 할 수도 있다.
- 분명히 이런 문제들은 우선적으로 피하려고 노력해야 하는 것들이다.
- 이런 활동은 결과적 일관성이 기술 주도가 아닌, 비즈니스 주도라는 것을 보여준다.
- 물론, 이전 장의 컨텍스트 매핑에서 논했던 것처럼 다수의 애그리게잇 사이에 갱신을 위한 기술적인 방안도 찾아야 한다.
- 그렇지만 다양한 엔티티 간에 발생하는 갱신의 수용 가능한 소요 시간을 결정할 수 있는 것은 오직
비즈니스
다. - 이는 즉시 또는 적절한 트랜잭션으로 처리되어야 하는 것들은 동일한 애그리게잇으로 관리해야 한다는 의미다.
- 또한, 결과적인 일관성이 필요한 경우에는 메시징과 같은 도메인 이벤트를 통해 관리해야 한다는 의미다.
- 비즈니스가 해야 하는 일이 무엇인지를 고려하는 것은 매우 중요하다.
- 실제 비즈니스가 어떤 일을 수행해야 하는지를 생각하는 것은 다양한 도메인 오퍼레이션들이 비즈니스 행위를 모델링한 소프트웨어에 통찰을 줄 것이고, 이는 비즈니스에 가치를 전달해줄 것이다.
- 단위 테스트를 위해 애그리게잇을 철저하게 캡슐화되도록 설계하자.
- 복잡한 애그리게잇은 테스트하기도 힘들다.
- 이전의 설계 가이드들은 테스트할 수 있는 애그리게잇을 모델링할 수 있는 좋은 지침들을 제공한다.
- 단위 테스트는 비즈니스 명세 검증(인수 테스트)과는 다르다.
- 단위 테스트 만드는 일은 인수 테스트에 관한 시나리오 명세 만드는 과정을 따라할 것이다.
- 여기서 고려해야 하는 것은 애그리게잇이 수행하길 기대하는 대로 정확하게 수행되는지 테스트하는 것이다.
- 또한, 애그리게잇의 모든 오퍼레이션이 정확성, 품질, 안정성을 보장하길 원할 것이다.
- 이를 위해 단위 테스트 프레임워크를 사용할 수도 있고, 다른 효과적인 단위 테스트 방법에 대한 많은 자료들을 활용할 수도 있다.
- 단위 테스트들은 바운디드 컨텍스트와 직접적으로 연관되는 것으로 해당 소스 코드 리파지토리에 보관될 것이다.
- 도메인 이벤트는 바운디드 컨텍스트 내의 비즈니스 관점에서 중요한 사항들에 대한 기록이다.
- 도메인 이벤트가 전략적 설계를 위해 매우 중요한 도구이다.
- 그 뿐만 아니라 종종 전술적 설계를 하는 동안 도메인 이벤트의 개념이 정립되면서 핵심 도메인의 일부가 된다.
바운디드 컨텍스트 내에 도메인 이벤트를 효과적으로 설계하고 구현하는 방법과, 도메인 이벤트를 어떻게 사용하는지에 대한 사례를 살펴보자.