OrderItem, Value Object일까 Entity일까
TL;DR
OrderItem을 처음엔 Value Object로 설계했지만, 도메인을 더 깊게 이해하면서 Entity로 두는 게 더 적절하다고 판단했다.
들어가며
도메인 설계는 언제나 쉽지 않다. 이번에도 이커머스 주문 도메인을 설계하면서 많은 고민이 있었다. 특히 OrderItem을 Value Object로 볼지, Entity로 볼지를 두고 꽤 오랫동안 고민했다.
초기 요구사항은 단순했다. 식별자가 필요 없었기 때문에 “그렇다면 Value Object로 두는 게 맞겠지”라고 판단했다. 하지만 루퍼스 멘토링 세션과 주변 개발자들과의 대화를 거치며 “정말 이 선택이 충분히 타당한가?” 하는 의문이 생겼다.
이번 글에서는 Entity와 Value Object의 차이를 다시 공부하며, 내가 이해한 기준과 판단 과정을 정리해보려 한다.
Entity vs Value Object
Entity와 Value Object 중 어느 쪽을 써야 할지 꽤 오랫동안 고민했다.
두 개념의 차이를 정확히 이해하지 못했기 때문에 더 선택이 어려웠던 것 같다.
그래서 먼저 이 개념들이 어디에서 나왔는지, 각각 어떤 정의를 가지는지부터 정리했다.
먼저 알고 있으면 좋은 용어가 있다.
- 도메인(Domain): 소프트웨어가 해결하고자 하는 문제 영역
- 도메인 모델(Domain Model): 그 문제 영역을 개념적으로 표현한 모델
즉, Entity와 Value Object는 도메인 모델을 구성하는 핵심 단위다.
이제 각 정의와 차이점에 대해 살펴보자.
Entity와 Value Object
Entity
- 고유한 식별자(ID)를 가진 객체
- 생성 → 변경 → 삭제되는 자신의 생명주기(Lifecycle)를 갖는다
- 도메인의 핵심 개념을 표현하며, 데이터를 다루는 기능도 함께 가진다
- 예: 주문(
Order), 사용자(User), 상품(Product)
Value Object
- 식별자가 없는, 말 그대로 “값(value)” 그 자체를 표현하는 객체
- 주로 엔티티의 속성으로 사용되며, 값이 같으면 완전히 동일한 것으로 본다
- 일반적으로 불변(Immutable)하게 다뤄진다
- 예: 주소(
Address), 금액(Money), 기간(Period)
이 두 개념은 아래 3가지 측면에서 차이점이 있다.
- 동등성
- 라이프사이클
- 불변
1. 동등성(Equality)
첫 번째 차이점은 객체가 동일한지 비교하는 방식이다.
정의에 따르면, Entity는 ID가 같으면 같은 존재로 본다.
즉, 식별자가 동등하면 동일한 객체로 간주한다.
예를 들어 주문번호가 같다면, 두 Order 인스턴스가 달라도 같은 주문이다.
반면 Value Object는 내부 값이 완전히 같을 때만 동일하다고 본다.
즉, 구조적 동등성(Structural Equality)이 같아야 한다.
예를 들어 Address("서울시", "강남구") 두 개는 똑같은 주소다.
비유하자면,
“Entity는 주민등록번호, Value Object는 1달러 지폐.”
2. 라이프사이클(Lifecycle)
두 번째 차이점은 시간의 흐름에 따라 변할 수 있느냐이다.
정의에 따르면, Entity는 고유한 생명주기(Lifecycle)를 가진다.
생성되고, 변경되고, 사라지며, 그 과정에서 상태가 바뀔 수 있다.
예를 들어 주문은 생성된 뒤 결제나 배송 상태로 바뀌고, 나중에 취소될 수도 있다.
반면 Value Object는 특정 시점의 상태를 표현하는 스냅샷에 가깝다.
값 자체는 변하지 않으며, 값이 바뀌면 새로운 객체로 대체된다.
비유하자면,
“Entity는 사람(Person), Value Object는 그 사람의 나이(Age).”
사람은 변하지만, ‘나이 30’이라는 값은 그 순간의 상태로 남는다.
3. 불변성(Immutability)
세 번째 차이점은 객체의 변경 방식을 어떻게 다루느냐이다.
Entity는 상태가 바뀔 수 있다.
결제나 배송 상태처럼, 시간이 지나면서 속성이 변한다.
그래서 동일한 Entity라도 수정(Update)을 통해 상태를 갱신한다.
반면 Value Object는 한 번 만들어지면 끝이다.
값을 바꾸려면 기존 객체를 수정하지 않고,
새로운 인스턴스(New Instance)를 만들어야 한다.
이 불변성 덕분에 Value Object는 예상치 못한 부작용이나 동시성 문제로부터 비교적 안전하다.
비유하자면,
“Entity는 수정 가능한 문서, Value Object는 최종본 PDF 사본.”
문서는 계속 바뀔 수 있지만, PDF는 한 번 저장되면 그대로 유지된다.
Value Object로 볼 때의 장단점
OrderItem을 Value Object로 두면 설계가 단순해진다.
별도의 식별자나 테이블을 만들 필요가 없고,
항상 Order 엔티티 내부에서 함께 관리된다.
즉, OrderItem은 Order의 한 부분으로 동작하며
별도의 생명주기나 독립 저장소를 고려하지 않아도 된다.
장점
- 구현이 단순하다
→@ElementCollection으로 매핑하면 추가 엔티티 없이 바로 사용할 수 있다. - 일관성 유지가 쉽다
→ Order와 함께 저장·삭제되므로 데이터 정합성 걱정이 적다. - 불변성 보장
→ 주문 항목의 상태를 변경하지 않고, 항상 새로운 값으로 대체한다.
단점
- 개별 조작이 어렵다
→ 특정 주문 항목만 취소하거나 수정하려면, 전체 주문을 다시 저장해야 한다. - 추적과 히스토리 관리가 불가능하다
→ OrderItem의 변경 내역이나 상태 전이를 표현할 수 없다. - 확장성 제약
→ 배송, 환불, 정산 같은 기능이 필요해지면 Entity로 변경이 필요하다.
결국 Value Object로 두면 현재 요구사항을 만족하기엔 충분하지만, 도메인이 확장되거나 상태 관리가 필요해지는 순간 한계에 부딪히게 된다.
Entity로 볼 때의 장단점
OrderItem을 Entity로 두면 설계가 조금 더 구조화되고,
복잡한 도메인 요구를 유연하게 다룰 수 있다.
특히 식별이 필요한 순간, 상태가 생긴 순간, 독립적인 수명주기가 발생하면
Entity로의 전환이 자연스러운 선택이 된다.
장점
- 개별 식별이 가능하다
→orderItemId를 통해 특정 주문 항목만 조회·취소·환불할 수 있다. - 상태 변화 추적이 가능하다
→ 배송, 환불, 정산 등 시간에 따라 변하는 속성을 다룰 수 있다. - 도메인 확장에 유리하다
→ 통계, 로그, 이력 관리처럼 OrderItem만 독립적으로 활용할 수 있다.
단점
- 구현 부담이 늘어날 수 있다
→ 별도의 테이블, 식별자, 매핑 관리가 필요하다. - 일관성 관리 비용이 생긴다
→ Order와 OrderItem이 분리되면, 상태를 함께 맞춰주는 동기화 로직이 필요하다.
예를 들어 주문을 취소하면 하위 항목도 함께 취소되도록 관리해야 한다. - 도메인 경계 설정이 중요하다
→ 설계가 잘못되면 책임이 겹치거나, 서로를 참조하는 순환 구조가 생길 수 있다.
예를 들어 Order와 OrderItem이 각각 배송비 계산 로직을 중복 가지는 상황을 피해야 한다.
비유하자면,
“Entity는 복잡한 도메인 요구를 담는 그릇이고, Value Object는 단순한 상태를 표현하는 값이다.”
결론
처음에는 요구사항이 단순했기 때문에 Value Object로 두는 게 절절하다고 생각했다.
이후 다시 일반적인 사용자 시나리오를 고려해봤을 때, 주문 항목은 단순한 값이 아니라 배송, 환불, 정산 등 기능이 필요한 주체였다.
그래서 Entity로 사용하는 것이 앞으로의 확장성과 유지보수를 생각했을 때도 더 적절한 선택이라고 판단했다.
배운 점
이번 과정을 통해, 설계에서 도메인을 어떻게 나누느냐가 가장 많은 고민이 필요한 부분임을 느꼈다.
도메인의 맥락을 읽고 변화 가능성을 고려하는 일은 여전히 경험과 직관이 중요한 영역이라, 이 부분만큼은 아직 AI보다 사람이 더 잘하는 일이라고 생각한다.
또한 루퍼스 동료분들과의 대화를 통해 Root Aggregate, Bounded Context 같은 도메인 주도 설계(DDD)의 개념도 새롭게 접했다.
아직은 낯설지만, 이번 경험을 계기로 이런 도메인 개념들을 더 깊이 이해하고 실제 설계에 녹여보고 싶다.