들어가며
도메인 모델을 설계할 때 고민이 되는 것 중 하나는 "어떤 것을 도메인으로 둘 것인가, 어떤 것을 VO로 둘 것인가"이다.
단순히 DB 관점에서 ID 존재 여부로 나누는 것이 맞을까?
이커머스 서비스를 설계하면서 이런 고민을 깊이 있게 해봤다. 특히 주문과 배송 사이에서 어떻게 나눌지에 대한 고민을 해봤다.
그래서 이 사례를 예시로 들어 도메인과 값 객체의 분리 기준과 설계 고민에 대해 글을 쓰고자 한다.
요구사항
사용자는 상품을 주문할 때 배송 정보를 함께 입력해야 한다.
- 수령인 이름, 수령인 전화번호
- 기본 주소, 상세 주소
도메인과 VO로 나누기
배송 정보를 VO로 추출
처음에는 주문 객체에 배송 정보(수령인 이름, 수령인 전화번호, 주소)를 몰아넣는 설계를 생각했다.
class Order {
private Long id;
private String receiverName;
private String receiverPhone;
private String baseAddress;
private String detailAddress;
...
}
하지만 이렇게 설계하면 Order 객체에 책임이 너무 몰리게 된다고 판단했다.
그래서 배송 관련 책임을 Delivery VO로 분리했다.
class Order {
private Long id;
private Delivery delivery; // 배송 정보를 VO로 분리
...
}
class Delivery {
private Receiver receiver;
private Address address;
...
}
그 이유를 정리하면
- 독립적인 생명주기가 없다
- 배송 정보는 주문 없이 존재할 수 없다.
- 주문이 생성될 때 함께 생성되고 주문이 삭제되면 함께 삭제되어야 한다.
- 즉, 독립적인 식별자가 필요하지 않고, 단독 조회가 필요하지 않기 때문에 값 객체로 충분하다고 판단했다.
- 책임 분리
- Order가 배송까지 책임을 지지 않도록, 배송 관련 로직을 Delivery VO로 캡슐화했다.
VO로 분리하면 안 되는 경우
주문 도메인에는 배송 정보 외에도 주문 상품 정보(OrderItem)가 있다.
OrderItem은 주문한 상품과 수량, 가격 등 주문 항목 하나를 나타내는 도메인 객체이다.
하나의 주문(Order)은 여러 개의 주문 항목(OrderItem)을 가질 수 있다.

그럼 왜 OrderItem은 VO가 아닌 엔티티인가?
OrderItem도 Order에 종속된 것처럼 보인다. 하지만 아래와 같은 이유로 엔티티로 설계해야 한다고 판단했다.
- "어떤 주문 상품"인지 구별할 식별자가 필요하다 (1:N 관계)
- Delivery는 Order와 1:1관계이기 때문에 주문 하나의 배송 정보는 단 하나이다.
- 하지만 OrderItem은 Order와 1:N 관계이다. 즉, 하나의 주문에 여러 개의 주문 상품을 가질 수 있고 이걸 식별할 식별자가 필요하다.
- 생명주기가 주문(Order)에 종속적이지 않다
- 주문 상품을 주문과 함께 생성되지만, 이후 개별ㅈ거으로 조회되거나 환불/취소같은 별도의 행위를 가질 수 있다.
- 따라서 단순히 Order 내부에 포함된 값이 아니라 독립적인 엔티티의 생명주기를 가진다고 생각한다.
VO와 엔티티를 구분하는 핵심은 "독립적인 식별자와 생명주기를 가지는가"인 것 같다.
식별자 없이 속성 값으로만 동일성을 판단하고, 부모 엔티티에 종속되어 존재한다면 VO로 설계하였다.
마치며
대학교 수업에서 소프트웨어 설계를 배울 때는
"클래스 다이어그램은 이런 식으로 그리고, ERD는 어떻게 표현해야 한다"처럼 다이어그램을 그리는 방법을 집중해서 배운다.
하지만 이번에 직접 도메인 설계를 하면서 느낀 건, 다이어그램을 "그리는" 게 중요한 게 아니라,
요구사항을 명확하게 정의하고, 그 요구사항을 가장 잘 표현할 수 있는 도메인을 "설계"하는 과정이 훨씬 중요하다는 것이다.
"이 기능은 왜 필요하지?", "누가 어떤 역할을 해야 하지?"와 같은 고민을 끊임없이 하면서 설계를 하는 게 중요하다는 걸 배울 수 있었다.