상위의 글이 DDD의 전략적 설계를 다룬다면 하기 글은 전술적 설계에 대한 내용을 다룬다.
전술적 설계는 DDD를 달성하기 위해 필요한 코드 레벨에 관한 내용을 주로 다룬다
- 엔티티
- 데이터 모델에 대응되는 객체가 아닌 비즈니스의 한 개념
- 나머지 엔티티와 반드시 구분 가능하며 고유 식별자를 가진다
- 초기 설계에는 식별자 중심적인 특성과 행동 그리고 이를 쿼리하는 요소에 의도적으로 집중하자
- 식별자 생성 전략
- 사용자(클라이언트)가 제공: 좋은 방법이 아니다 → 충돌이나 시간차 등 다양한 요소에 의해 방해받는다
- 어플리케이션이 생성: GUID, UUID 등을 이용하여 생성한다
- 영속성 매커니즘이 생성: 영속성 저장소가 자동 생성하도록 유도
- 다른 바운디드 컨텍스트가 제공: 최대한 쓰지 마라… 그냥 하지 마라
- 객체 정신 분열증: 위임된 객체가 위임받기 전 본래 객체의 식별자를 모르는 상황에서 나타낸다.
- 더 작은 단위의 인터페이스가 필요하다는 증거이다
- 어떤 역할인지 알기 위해 위임으로 구현된 객체는 반드시 본래 객체의 식별자를 지니도록 하자
- 복잡한 인스턴스의 생성은 팩토리를 이용하자
- 유효성 검사는 모델 내에서 일어나야 한다.
- 별도의 유효성 검사 클래스가 필요할 땐 엔터티와 같은 모듈에 두자
- 전체 객체 컴포지션의 유효성 검사는 도메인 서비스를 이용하자
- 값 객체
- 도메인 내의 어떤 대상을 측정하고, 수량화하고, 설명하는 용도의 객체
- 특징
- 불변성이 유지된다
- 여러 속성을 모아 하나의 개념을 설명 할 수 있어야 한다
- 타입과 특성이 모두 같다면 해당 값은 같은 것으로 간주되어야 한다
- 고유 식별자가 없다
- 가이드
- 엔티티는 값 객체의 컨테이너로 동작하도록 구성해야 더 좋은 방식이다
- 메소드의 매개변수는 일반적인 값(객체 X)만으로 전달하도록 하자
- 수명 주기에 따른 변경의 연속성이 필요없다면 값 객체로 만들자
- 표준타입(enum 과 같은) 은 매우 신중하게 사용해야 한다 → 아니면 열거형 클래스를 사용하자
- 값 객체의 테스트는 도메인 의미가 있어야 한다
- 값 객체의 데이터베이스 비 정규화
- 저자는 도메인 모델을 위해 값 객체는 비 정규화를 권장하고 있다.
- 고려해야 할 점
- 열 넓이: varchar 의 최대 길이에 주의 하라
- 쿼리 해야 하는 경우: 값 객체로 쿼리를 해야 하는 경우라면 설계를 다시 한번 고민해보자
- 사용자 지정 유저 타입: 주의해서 사용하자. 데이터베이스 마이그레이션이 어려워진다
- 엔티티의 컬렉션 값 객체의 저장
- 대리키를 이용한 방식으로 저장하라
- 대리키를 가진 마커 클래스를 생성하여 상속으로 이용하라
- 마커 클래스: 클래스의 type을 나타내는 플래그 방식의 interface
- 어그리게잇 = 트랜잭션 일관성 경계
- 데이터 변경의 단위로 다루는 연관 객체의 묶음을 말한다
- 어그리게잇 루트는 언제나 엔티티이다
- 어그리게잇 내부에서는 레포지토리를 참조하지 않으려고 노력해야 한다
- 한 트랜잭션은 단 하나의 어그리게잇만을 변경해야 한다
- 잘못된 설계
- 너무 큰 클러스터: 실제 비즈니스 규칙이 아닌 잘못된 고정자**(불변 값 객체)**를 기준으로 설계된 경우
- 다수의 어그리게잇: 너무 잘게 쪼개진 어그리게잇 때문에 트랜잭션 처리를 할 수 없음
- 가이드
- 제대로 된 고정자(일관성 유지)를 유지하라
- 어떤 오퍼레이션이 실행되든 관계없이 경계 안의 모든 대상이 특정 비즈니스 고정자 규칙 집합을 준수하도록 보장해야 한다
- 시간에 따라 변화한다면 엔티티로, 완전히 대체된다면 값 객체로 설계해야 한다
- 규칙
- 어그리게잇끼리의 참조
- Id 로만 해야 한다 → 한 트랜잭션에서 여러 어그리게잇을 수정하려는 유혹을 피할 수 있다
- 즉, 추론 객체 참조를 이용하라.
- 참조 모델의 조회가 필요할 때는 지연 로딩 또는 미리 객체를 조회하여 어그리게잇 생성자에 삽입하는 방법이 있다
- 경계의 밖은 결과적 일관성만을 허용한다
- 도메인 이벤트를 이용하여 경계의 바깥 도메인은 결과적 일관성으로 움직여야 한다
- 다른 사용자나 시스템이 해야 할 일이라면 결과적 일관성을, 유즈케이스를 수행하는 사용자가 일관성을 보장해야 한다면 트랜잭션을 사용해야 한다
- 규칙을 어길 수 있는 예외의 상황
- 사용자 인터페이스 편의: 주로 배치 생성에서 사용된다
- 기술적 매커니즘 부족: 제대로 된 인프라 부족으로 인해 어기는게 더 좋을 때
- 글로벌 트랜잭션: 2단계 커밋 트랜잭션이 반드시 지켜져야 하는 경우
- 쿼리 성능: 영속성 저장소의 성능 문제로 인해 불가피하게 사용해야 하는 경우
- 구현
- 고유 ID와 루트 엔티티 지정하기
- 어그리게잇의 파트를 값 객체로 모델링 하기
- 클라이언트는 서버에 대한 지시자의 입장이 되어야 한다
- 낙관적 동시성에 대한 버저닝은 1. 루트만 2. 모든 엔터티 두 가지 방식이 있다
- 리파짓토리나 도메인 서비스의 의존성 주입을 피해라
- 이벤트 ( 도메인 이벤트 )
- 도메인에서 과거에 일어난 일련의 이벤트
- 어그리게잇에서 주로 발행하며, 결과적 일관성을 보장하기 위해 주로 이용됨
- 이벤트 자신이 클라이언트로부터 생성된다면 어그리게잇이 될 수 있다
- 결과적 일관성을 달성하기 위해서 일관성 달성 지연 시간을 허용해야 한다
- 주로 어플리케이션 서비스에서 도메인 이벤트의 구독자를 등록하게 된다
- 설계 가이드
- 일어난 사실을 반영하는 이름으로 네이밍하자
- 불변으로 생성되며 각 속성의 읽기 접근자를 포함한다
- 중복 관리를 위해 고유 식별자를 사용 할 수 있다
- 최종 일관성 유지 방안
- 자치 서비스에서는 이러한 이벤트를 통해 데이터를 직접 복사하여 이용하기도 한다 → 다만 객체를 정확히 전부 복사하는 경우라면 문제라고 판단해야 한다
- 이벤트 저장소
- 아키텍처 스타일