1) 엔티티와 밸류
- 도출한 모델은 크게 엔티티(Entity)와 밸류(value)로 구분할 수 있다.
엔티티와 밸류를 제대로 구분해야 도메인을 올바르게 설계하고 구현할 수 있기 때문에
이 둘의 차이를 명확하게 이해하는 것은 도메인을 구현하는데 있어 중요하다.
(1) 엔티티
- 엔티티의 가장 큰 특징은 식별자를 가진다는 것이다.
식별자는 엔티티 객체마다 고유해서 각 엔티티는 서로 다른 식별자를 갖는다.
예를 들어, 주문 도메인에서 각 주문은 주문번호를 가지고 있는데,
이 주문번호는 각 주문마다 서로 다르다.
따라서 주문번호가 주문의 식별자가 된다.
- 주문 도메인 모델에서 주문에 해당하는 클래스가 Order이므로
Order가 엔티티가 되며 주문번호를 속성으로 갖게 된다.
Order
-orderNumber: String
-orderLines: List<OrderLine>
-totalAmounts: Money
-shippingInfo: ShippingInfo
-state: OrderState
- 주문에서 배송지 주소가 바뀌거나 상태가 바뀌더라도 주문번호가 바뀌지 않는 것처럼
엔티티의 식별자는 바뀌지 않는다.
엔티티를 생성하고 속성을 바꾸고 삭제할 때까지 식별자는 유지된다.
- 엔티티의 식별자는 바뀌지 않고 고유하기 때문에
두 엔티티 객체의 식별자가 같으면 두 엔티티는 같다고 판단할 수 있다.
엔티티를 구현한 클래스는 다음과 같이 식별자를 이용해서 equals() 메서드와 hashCode() 메서드를 구현할 수 있다.
public class Order {
private String orderNumber;
@Override
public boolean equals(Object obj){
if(this == obj) return true;
if(obj == null) return false;
if(obj.getClasee() != Order.class) return false;
Order other = (Order) obj;
if(this.orderNumber == null) return false;
return this.orderNumber.equals(other.orderNumber);
}
@Override
public int hashCode(){
final int prime = 31;
int result = 1;
result = prime*result+ ((orderNumber == null)? 0 : orderNumber.hashCode());
return result;
}
}
(2) 엔티티의 식별자 생성
- 엔티티의 식별자를 생성하는 시점은 도메인의 특징과 사용하는 기술에 따라 달라진다.
흔히 식별자는 다음 중 한 가지 방식으로 생성한다.
(1) 특정 규칙에 따라 생성
(2) UUID나 Nano ID와 같은 고유 식별자 생성기 사용
(3) 값을 직접 입력
(4) 일련번호 사용(시퀀스나 DB의 자동 증가 칼럼 사용)
- 주문번호, 운송장번호, 카드번호와 같은 식별자는 특정 규칙에 따라 생성한다.
이 규칙은 도메인에 따라 다르고, 같은 주문번호라도 회사마다 다르다.
예를 들어, 최근에 두 온라인 서점에서 구매한 책의 주문번호는
각각 '2021112831728OOOO'와 '001-A88277OOOO'인데
두 번호의 구조가 완전히 다른 것을 알 수 있다.
- 흔히 사용하는 규칙은 현재 시간과 다른 값을 함께 조합하는 것이다.
한 온라인 쇼핑 사이트에서 구매한 책의 주문번호는 '2021112831728OOOO'인데,
이 주문번호의 앞 번호인 '2021128'는 2021년 11월 28일을 의미한다.
- 날짜와 시간을 이용해서 식별자를 생성할 때, 주의할 점은
같은 시간에 동시에 식별자를 생성해도 같은 식별자가 만들어지면 안된다는 것이다.
- UUID를 사용해서 식별자를 생성할 수 있다.
다수의 개발 언어가 UUID 생성기를 제공하고 있으므로,
마땅한 규칙이 없다면 UUID를 식별자로 사용해도 된다.
자바는 java.util.UUID 클래스를 사용해서 UUID를 생성할 수 있다.
UUID uuid = UUID.randomUUID();
String struuid = uuid.toString();
- 회원의 아이디나 이메일 같은 식별자는 값을 직접 입력한다.
사용자가 직접 입력하는 값이기 때문에 식별자를 중복해서 입력하지 않도록 사전에 방지하는 것이 중요하다.
(3) 밸류 타입
- ShippingInfo 클래스는 다음과 같이 받는 사람과 주소에 대한 데이터를 갖고 있다.
public class ShippingInfo {
private String receiverName;
private String receiverPhoneNumber;
private String shippingAddress1;
private String shippingAddress2;
private String shippingZipcode;
... 생성자, getter
}
- ShippingInfo 클래스의 receiverName 필드와 receiverPhoneNumber 필드는
서로 다른 두 데이터를 담고 있지만, 두 필드는 개념적으로 받는 사람을 의미한다.
즉, 두 필드는 실제로 하나의 개념을 표현하고 있다.
비슷하게 shippingAddress1 필드, shippingAddress2 필드, shippingZipcode 필드는
주소라는 하나의 개념을 표현한다.
- 밸류 타입은 개념적으로 완전한 하나를 표현할 때 사용한다.
예를 들어 받는 사람을 위한 밸류 타입인 Receiver를 다음과 같이 작성할 수 있다.
public class Receiver {
private String name;
private String phoneNumber;
public Receiver(String name, String phoneNumber){
this.name = name;
this.phoneNumber = phoneNumber;
}
public String getName(){
return name;
}
public String getPhoneNumber() {
return phoneNumber;
}
}
- Receiver는 '받는 사람'이라는 도메인 개념을 표현한다.
앞서 ShippingInfo의 receiverName 필드와 receiverPhoneNumber 필드가
이름을 갖고 받는 사람과 관련된 데이터라는 것을 유추한다면
Receiver는 그 자체로 받는 사람을 뜻한다.
밸류 타입을 사용함으로써 개념적으로 완전한 하나를 잘 표현할 수 있는 것이다.
- ShippingInfo의 주소 관련 데이터도 다음의 Address 밸류 타입을 사용해서 보다 명확하게 표현할 수 있다.
public class Address {
private String address1;
private String address2;
private String zipcode;
public Address(String address1, String address2, String zipcode){
this.address1 = address1;
this.address2 = address2;
this.zipcode = zipcode;
}
...
}
- 밸류 타입을 이용해서 ShippingInfo 클래스를 다시 구현해보자.
배송정보가 받는 사람과 주소로 구성된다는 것을 쉽게 알 수 있다.
public class ShippingInfo {
private Receiver receiver;
private Address address;
... 생성자, get 메서드
}
'도메인 주도 개발 시작하기' 카테고리의 다른 글
[도메인 주도 개발 시작하기] 애그리거트의 영속성 전파 (0) | 2025.01.14 |
---|---|
[도메인 주도 개발 시작하기] 애그리거트 로딩 전략 (0) | 2025.01.14 |
[도메인 주도 개발 시작하기] 바운디드 컨텍스트 (0) | 2025.01.14 |
[도메인 주도 개발 시작하기] 애그리거트 (0) | 2025.01.07 |
[도메인 주도 개발 시작하기] 도메인 모델 패턴 (0) | 2025.01.07 |