최범균 님의 DDD Start를 읽고 정리한 내용입니다.
도메인
도메인 모델
이커머스에서 모니터를 구매한다고 가정해보자.
- 원하는 스펙의 모니터를
검색
하고 다양한 물건을 비교한다. - 눈에 띄는 물건이 여러 개라면
장바구니
에 넣어둘 수도 있다. 구매
를 결정했다면 어떤 결제수단을 사용할지, 배송지는 어딘지를 선택한다.- 배송을 시작했다면
배송 추적
기능을 활용하여 내 물건이 어디쯤 왔는지를 확인한다.
개발자에겐 쇼핑몰
은 구현 대상이다.
그리고 쇼핑몰
에서는 구매를 위한 다양한 기능을 제공한다.
여기서 쇼핑몰
은 소프트웨어로 해결하기 위한 도메인에 해당한다.
정의
도메인 모델은 특정 도메인을 개념적으로 표현한 것이다.
비즈니스 로직에 맞는 규칙을 구현한 레이어는 도메인이 되어야 한다.
예시
상품 주문을 예시로 코드를 작성한다.
코드 작성보다 선행되어야 할 것은 모델링이다.
모델의 규칙 정의
비즈니스 요구사항에 맞춰 규칙을 정의해보자.
- 한 종류 이상의 상품을 주문해야 한다.
- 한 상품을 한 개 이상 주문할 수 있다.
- 총 주문 금액은 각 상품의 구매가 합을 의미한다.
- 각 상품의 구매가격 합은 상품 가격 * 구매 수량을 의미한다.
규칙이 다 정의되었으면 상태 다이어그램도 그려본다.
코드 작성
위 규칙과 다이어그램을 바탕으로 주문 정보를 가질 객체 Order
를 정의한다.
public class Order {
private OrderNo id;
private List<OrderLine> orderLines;
private OrderState state;
private ShippingInfo shippingInfo;
private Money totalAmounts;
public void payment() { ... }
public void shipped() { ... }
public void startDelivery() { ... }
public void completeDelivery() { ... }
public void cancel() { ... }
public void changeShippingInfo(ShippingInfo newShippingInfo) { ... }
}
Order
에는 결제, 출고, 배송시작, 배송완료, 취소, 배송 정보 변경 기능이 포함되어 있다.
OrderLine
은 주문 정보를 의미한다. 상품 종류, 가격, 개수 등이 포함된다.
OrderState
는 주문의 상태 정보이다.
ShippingInfo
는 배송지 정보를 나타낸다.
Money
는 금액을 나타내는 밸류 타입의 객체로 사용한다.
- 밸류 타입은 개념적으로 하나인 무언가를 표현하기 위해 사용한다.
- Money 대신 int를 사용해도 된다. Money는 int보다 더 명시적인 표현으로 개발자의 이해를 돕는다.
다른 주요 객체도 살펴보자.
OrderLine
public class OrderLine { private Product product; private Money price; private int quantity; private Money amounts; }
OrderState
public enum OrderState { PAYMENT_WAITING { public boolean isShippingChangeable() { return true; } }, PREPARING { public boolean isShippingChangeable() { return true; } }, SHIPPED, DELIVERING, DELIVERING_COMPLETED, CANCELED; public boolean isShippingChangeable() { return false; } }
- isShippingChangeable 메소드로 배송지 변경 가능 여부를 판별한다.
ShippingInfo
public class ShippingInfo { private Receiver receiver; private Address address; }
코드를 전부 작성하면 테스트도 작성해보자.
- 주문 실패
- 받는 사람 정보가 없는 경우
- 배송지 정보가 없는 경우
- 주문 성공
- 각 단계를 무사히 넘어가는가?
- 주문 취소
- 지정된 단계에서만 취소가 가능한지?
- 취소가 올바르게 되었는지?
- 배송지 정보 변경
- 지정된 단계에서만 변경이 가능한지?
- 변경된 배송지가 올바르게 적용되었는가?
주의점
DDD 패턴으로 작성할 때는 몇 가지 신경써야 할 것이 있다.
도메인 객체는 get/set 메소드를 넣지 않는다.
- get/set은 도메인의 핵심 개념이나 의도를 표현하지 못한다.
- 도메인은 해당 개념이 어떤 행위를 하고, 어떻게 상태가 변하는 지에 집중한다.
- set/get으로 객체의 상태값을 계속 바꿔서 사용하는 행위는 '객체지향'보다 '절차지향'에 가깝다.
도메인 객체는 완전한 상태로 사용한다.
- 주문을 하기 위해
OrderLine
,ShippingInfo
는 반드시 포함되어야 한다. Order
생성 후 정보를 추가한다면 정보의 일부가 누락되는 상황이 생길 수 있다.- 주문 정보가 없다면?
- 배송지 정보가 없다면?
- 주문을 하기 위해
도메인 용어를 명확히 정의한다.
- 상품이 준비되어서 배송 올 때까지의 단계를 Step 1 ~ Step N으로 정의했다고 생각해보자.
- 가독성이 크게 떨어진다.
- OrderState.PAYMENT_WAITTING처럼 상태에 대해 명확하게 정의한 단어는 개발자가 소스 코드를 이해하는데 필요한 시간을 줄인다.
밸류 타입은 불변 객체로 사용한다.
값의 변화를 허용하면 잘못된 참조로 인한 오류가 발생할 수 있다.
Money price = new Money(50000); OrderLine line = new OrderLine(product, price, quantity); price.setValue(30000); // line.getPrice().getValue()가 갖는 값은?
전체 코드는 Github에 업로드되어 있다.
'Java > Domain Driven Design' 카테고리의 다른 글
DDD 도메인 모델의 군집, 애그리거트(Aggregate) (0) | 2020.04.06 |
---|---|
DDD 아키텍처 (0) | 2020.03.18 |