저번에 도메인 모델이 무엇인지 어렴풋이 개념을 잡았다.
도메인 주도 개발이라 함은 도메인 모델, Entity, Value Object가 같이 오는데 그 개념에 대해서 알아보려고 한다.
도메인 모델?
비즈니스 개념으로 도메인을 분리한 것도 있지만 좀더 개발자 입장에서 파악하자면, 객체 지향적으로 하나의 객체에 핵심 규칙까지 포함하여 도메인을 모델링한 것으로 표현한다.
저번에 학습한 도메인 모델의 심화이다. 저번엔 간략하게 객체지향적인 도메인 모델을 구성해 봤는데, 실제 프로젝트는 훨씬 복잡할 것이다. 실제 프로젝트를 접했을때 한번에 도메인 모델을 도출하기엔 쉽지 않을 것이다. 도출을 위한 몇가지 과정을 살펴보려한다.
간략한 예시를 들어서 함께 모델링을 해보자!
[주문 도메인]
1. 최소 한개 이상의 상품을 주문할 수 있다.
2. 총 주문 금액은 각 상품의 구매 가격의 합이다.
3. 각 상품의 구매 가격은 상품 가격 x 구매개수 이다.
주문 도메인에 위와같은 간략한 요구사항을 받았다고 가정해본다.
대강 음 주문은 상품과 가격이 중요한 정보라는것을 파악 할 수 있다.
얼마 없는 정보를 토대로 주문이라는 도메인에는 결제라는 행동이 지침되어야한다고 파악할 수 있다.
위 정보를 토대로 생상자 및 각종 부가 정보를 뺀 아주우 간략한 객체를 생성해 보겠다.
//주문
public class Order {
// 결제 완료
public void completePayment() {...}
}
또 더 뜯어보게되면 상품과 수량정보가 나오는데 이 정보를 담을만한 주문정보 객체를 추출해 낼 수 있다.
//주문 정보
public class orderLine {
//상품정보
public Product product
//수량
public int amounts;
//가격
public int price;
//개수
public int quantity;
//이 객체의 필수 조건은 총합 계산이다
public int calculateAmounts() {
return price * quantity;
}
}
첫번째 요구사항인 '최소 한개 이상의 상품을 주문할 수 있다'는 다음과 같은 사항을 명시한다. 최소 한개의 OrderLine이 있어야 Order의 complatePayment를 사용할 수 있다.
또 '총 주문 금액은 각 상품의 구매 가격의 합이다'를 토대로 하나의 Order에 연결된 OrderLine의 합이 총 지불해야할 금액이라는 것을 알 수 있다.
이를 코드로 나타내면 다음과 같을 것이다.
public class Order {
//주문 상품 들 (최소 1개이상)
public List<OrderLine> orderLines;
//결제 완료
public void completePayment() {
//여기에 결제를 위해서는 한개 이상의 상품이 있어야 하는 규칙을 적용할 수 있지 않을까?
if(orderLines == null || orderLines.isEmpty()) {
throw new IllegalArgumentException("no orderline");
}
//지불해야하는 총 구매 가격의 합 도출
int sum = orderLines.stream()
.mapToInt(x -> x.getAmounts)
.sum();
return sum;
}
}
실제론 훨씬 복잡하겠지만 간략하게 요구사항을 기반으로 도메인모델을 점진적으로 만들어봤다.
앞서 도메인 모델을 도출해 보았다. 도출한 모델은 엔티티와 밸류로 구분된다.
엔티티?
고유한 식별자를 가지며 생명 주기에서 연속성을 가지는 객체
다들 엔티티 하면 식별자부터 언급한다. 첨 공부할때 와닿지 않은 이유에 이게 크지않을까 그래서 다들 예시를 드나보다. 나도 예시를 들려고한다.
ex1.
'홍길동'이라는 사람은 '주민번호'라는 고유한 식별자를 가지며 생애 주기에서 이 식별자는 변하지 않고 나이,성별,외모 같은 속성들이 변경되어도 '홍길동'이란 사람을 추적하게 해주는 연속성을 가진다.
ex2
'주문'이라는 객체는 '주문번호'라는 고유한 식별자를 가지며 해당 주문이 완료 될 때 까지 결제금액, 수량등 속성이 변경되어도 해당 '주문'은 추적할 수 있다는 것이다.
때문에 위의 예시에서 Order가 엔티티가 된다.

이 엔티티는 고유의 식별자로 동일한 객체임을 구분한다.
때문에 메모리를 비교하는 Object객체의 equals를 식별자로 판단할 수 있도록 재구현해주어야 한다.
식별자 생성은 다음과 같은 4가지 방식이 있다.
1. 특정 규칙에 따라 생성
2. UUID / Nano ID 와 같은 고유 식별자 생성기 사용
3. 값 입력
4. 일련번호 사용

Value Object
'값'이라는 의미보다는 개념적으로 동일하게 치부되는 값을 가진 객체이다.
위에서 정의한 OrderLine을 살펴보면 각 데이터가 의미하는 바는 다르지만 개념적으로는 하나의 정보를 가진다.

value 는 보통 불변하다고 한다. 값의 변경이 없다는 뜻이다.
그렇기에 값이 변동되어야할때는 새로운 밸류 객체를 생성하여 반환하는 식으로 사용이 된다.
두 밸류 객체를 비교해야할 경우에는 모든 속성의 값을 비교하는 equals를 재 정의 해야한다.
주문 상태 같은 상태값은 enum으로 표시하여 사용한다. 이때 각 단계를 STEP1, STEP2이런식으로 규정하면 가독성이 떨어지고 당사자 말고 타 개발자의 개발 시간도 더 소요된다. 대신 PAYMENT_WATING, PREPARING과 같은 직관적인 용어를 사용하자.
이렇게 해두면 각 상태값 사이의 규칙도 파악하기가 훨씬 수월하다.
이처럼 개발과 비즈니스를 직결할 수 있는 더 나은 용어를 발견한다면 해당 용어를 사용하여 비즈니스 이해를 높이고 개발 효율을 증폭시켜야한다.