도메인 주도 설계를 정리하는 주된 이유는 구현해야 할 문제, 즉 도메인(domain)을 어떻게 구현해야 지속가능한 application으로 개발할 수 있는지에 대한 답을 찾고자 함입니다.
이번 내용을 정리하면서 아래의 질문에 대한 답을 찾을 수 있으면 좋겠습니다.
- 도메인(domain)에 대한 이해와 도메인을 객체로 구현하려면?
- 엔티티(entity)는 어떻게 구현해야 하는지?
- entity와 RDB와의 Mapping에 대한 내용, 그리고 JPA에 대해
- domain 영역의 구성요소는 어떤 것들이 있는지?
- 객체 지향 기법과 도메인 설계 간 상관관계는? (캡슐화, 추상화, 다형성 등)
매 주 일요일에 포스트를 작성, 내용을 추가할 생각입니다.
주문 요구사항
요구사항에서 알 수 있는 것은 다음의 기능을 제공한다는 것.
주문 항목이 어떤 데이터로 구성되어야 하는지는 아래 요구사항에서 확인된다.
- 한 상품을 한 개 이상 주문할 수 있어야
- 각 상품의 구매 가격 합은 상품 가격에 구매 개수를 곱한 값
주문 - 주문항목 간의 관계는 다음의 요구사항에서 확인된다.
- 최소 한 종류 이상 상품을 주문해야
- 총 주문 금액은 각 상품의 구매 가격 합을 모두 더한 금액
배송지 정보는 받는 사람 이름, 전화번호, 주소
데이터를 가짐.
- 주문할 때 배송지 정보를 반드시 지정해야 한다.
가장 큰 특징은 식별자를 갖는다는 것.
entity 객체의 식별자는 생성, 속성 변경, 삭제의 과정까지 유지된다.
비교하는 두 entity 객체의 식별자가 같으면 두 객체는 같다고 판단할 수 있다.
식별자를 이용해서 equals()
와 hashCode()
를 구현할 수 있다.
public class OrderNo {
private String number;
private OrderNo() {
}
public OrderNo(String number) {
this.number = number;
}
public String getNumber() {
return number;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OrderNo orderNo = (OrderNo) o;
return Objects.equals(number, orderNo.number);
}
@Override
public int hashCode() {
return Objects.hash(number);
}
}
value type은 개념적으로 완전한 하나를 표현할 때 사용한다.
받는 사람(이름, 전화번호)는 receiver로 구현 가능하고, 수령지(주소1, 주소2, 우편번호)는 address로 구현 가능하다.
public ShippingInfo(Address address, String message, Receiver receiver) {
this.address = address;
this.message = message;
this.receiver = receiver;
}
...
public Address(String address1, String address2, String zipcode) {
...
}
필드의 명칭을 통해 객체를 유추하지 않고 valueType을 사용함으로써 객체를 완전한 개념으로 표현할 수 있음.
개념적으로 완전한 하나의 표현이 둘 이상의 필드(데이터)를 가져야 한다는 의미는 아니다.
public class Money {
private int value;
public Money(int value) {
this.money = money;
}
public int getValue() {
return this.value;
}
}
Money price
, Money amounts
로 명확한 개념으로 표현이 가능.value type을 사용하면 개념과 연관된 기능을 객체에 구현할 수 있음.
Money.add()
, Money.multiply()
돈 계산
이라는 의미로 명확히 작성할 수 있음. (가독성 향상!)value type의 데이터 변경 (setter) 은 field 값을 변경하는 것이 아니라 새로운 value 객체를 생성하는 방식으로 구현하는 것이 좋다. (불변(immutable)로 구현)
public class Money {
private int value;
public Money add(Money money) {
return new Money(this.value + money.value);
}
// value를 변경할 수 있는 메시드 없음
}
entity type의 두 객체가 같은지 비교할 때 ⇒ 식별자를 사용
두 value 객체가 같은지 비교할 때는 모든 속성이 같은지 비교해야 한다.
equals(), hashCode() 가 구성되어야 한다.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Receiver receiver = (Receiver) o;
return Objects.equals(name, receiver.name) && Objects.equals(phone, receiver.phone);
}
@Override
public int hashCode() {
return Objects.hash(name, phone);
}
getter, setter method를 무조건 추가하는 것은 좋지 않은 버릇이다!
도메인 지식을 코드로 구현하는 것이 자연스럽다.
domain 객체를 생성할 때 완전한 상태가 아닐 수 있는 상태에서 사용될 수 있는 문제
// set 메시드로 데이터를 전달하도록 구현하면
// 처음 Order를 생성하는 시점에 order는 완전하지 않다.
Order order = new Order();
// set 메서드로 필요한 모든 값을 전달해야 함
order.setOrderLine(lines);
order.setShippingInfo(shippingInfo);
// 주문자(Orderer)를 실징하지 않은 상태에서 주문 완료 처리
order.setState(OrderState.PREPARING);
// 도메인 객체가 불완전한 상태로 사용되는 것을 막으려면 생성 시점에 필요한 것을 전달해 주어야 한다.
// 생성자를 통해 필요한 데이터를 모두 받도록 한다.
Order order = new Order(orderer, lines, shippingInfo, OrderStarte.PREPARING);
setter를 private
지시자로 설정하여 constructor 에서 활용할 수 있음.
setter method를 구현해야 할 특별한 이유가 없다면 불변 타입의 장점을 살릴 수 있도록 value type은 불변으로 구현한다.
코드를 작성할 때 도메인에서 사용하는 용어는 매우 중요.
도메인 용어를 코드에 반영하지 않으면 그 코드를 해석할 때 의미를 확인해야하는 부담이 생긴다.
// 상품 준비중, 배송중, 배송완료됨
public OrderState {
STEP1, STEP2, STEP3
}
// 도메인 사용 용어를 최대한 반영하면 이해하기 용이하다.
public enum OrderState {
PREPARING, SHIPPED, DELIVERY_COMPLETED
}
step1, step2 를 비교한다거나 값을 확인한다거나 할 때 해당 변수명이 정확히 어떤 의미인지 확인하는 것 없이 코드를 구현할 가능성도 있고
현업이나 다른 개발자와의 협업 시에 '상품 준비중', '배송중' 으로 논의 중일 때 STEP1, STEP2로 해석해서 로직을 이해해야 한다.
도메인 용어를 코드에 사용하면 가독성이 높아지면서 코드를 분석하고 이해하는 시간을 절약한다.
도메인 용어를 영어로 해석하는 노력이 필요하다. (시간을 아까워하지 말자.)
안녕하세요. 정리된 글 잘 보았습니다.
혹시 참고하신 책이 어떤것인지 알 수 있을까요?