핵심 인사이트 (3줄 요약)
- 본질: 엔티티 (Entity)는 고유 식별자(ID)로 구별되는 도메인 객체로 시간이 지나도 동일성이 유지되는 반면, 값 객체 (Value Object)는 식별자 없이 속성 값 전체로 동일성이 결정되는 불변(Immutable) 도메인 개념으로, 복사·비교·교체가 자유롭다.
- 가치: 엔티티와 값 객체를 올바르게 구분하면 도메인 모델의 명확성이 높아지고, 값 객체의 불변성 덕분에 동시성 버그·복사 오류가 예방되며, 도메인 개념이 코드에 정확히 표현된다.
- 판단 포인트: 판단 기준은 '이 객체가 시간이 지나도 추적되어야 하는가?'다. 추적이 필요하면 엔티티(ID 부여), 추적 없이 값 자체가 의미를 가지면 값 객체(불변)로 설계한다.
Ⅰ. 개요 및 필요성
DDD에서 도메인 객체는 크게 엔티티와 값 객체로 분류된다. 이 구분은 비즈니스 도메인을 정확히 모델링하는 데 핵심적이다.
엔티티의 예: '주문 #1001'은 주문 내용이 바뀌어도(항목 추가, 상태 변경) 여전히 '주문 #1001'이다. 반면 '₩10,000'은 어디서 왔든 같은 금액이면 동일한 값 객체다. 같은 금액의 서로 다른 지폐를 구별할 필요가 없듯이.
┌─────────────────────────────────────────────────────────────┐
│ 엔티티 vs 값 객체 비교 │
├─────────────────────────────────────────────────────────────┤
│ 엔티티 (Entity) 값 객체 (Value Object) │
│ ───────────────── ─────────────────────── │
│ 고유 ID 존재 ID 없음 │
│ 가변 (Mutable) 불변 (Immutable) │
│ 참조 동일성 비교 값 동일성 비교 │
│ 생명주기 추적 교체 가능 │
│ │
│ 예: Order(orderId) 예: Money(amount, currency) │
│ User(userId) Address(city, street) │
└─────────────────────────────────────────────────────────────┘
- 📢 섹션 요약 비유: 사람(엔티티)은 이름이 바뀌어도 주민등록번호(ID)로 동일인이 확인된다. 반면 1만원 지폐(값 객체)는 어느 지폐든 1만원이면 동일하다.
Ⅱ. 아키텍처 및 핵심 원리
값 객체의 가장 중요한 특성은 불변성(Immutability)이다. 값 객체를 수정하는 대신 새 값 객체를 생성하여 교체한다. 불변성으로 인해 Thread-safe하고 공유가 안전하다.
| 항목 | 설명 | 포인트 |
|---|---|---|
| 동일성 기준 | 고유 ID | 모든 속성 값 |
| 가변성 | 가변(속성 변경 가능) | 불변(교체만 가능) |
| 생명주기 | 추적(영속화·조회 필요) | 추적 불필요 |
| 비교 방식 | ID 비교 | 속성 전체 비교 |
| 공유 가능성 | 위험(참조 공유 시 부작용) | 안전(불변이므로 공유 OK) |
┌─────────────────────────────────────────────────────────────┐
│ 값 객체 불변성 - 교체(Replace) 패턴 │
├─────────────────────────────────────────────────────────────┤
│ // 잘못된 방법 (가변 값 객체 - 버그 위험) │
│ price.setAmount(12000); // 기존 객체 수정 │
│ │
│ // 올바른 방법 (불변 값 객체 - 교체) │
│ Money newPrice = new Money(12000, "KRW"); │
│ order.changePrice(newPrice); // 새 객체로 교체 │
└─────────────────────────────────────────────────────────────┘
- 📢 섹션 요약 비유: 값 객체는 수표(Check)처럼 금액이 적혀 있으면(속성값) 어느 수표든 같다. 찢어지면 같은 금액의 새 수표(새 객체)를 발행하면 된다.
Ⅲ. 비교 및 연결
엔티티와 값 객체를 혼동하는 가장 흔한 실수는 'Address'를 엔티티로 설계하는 것이다. 주소는 주민번호가 없으므로 값 객체로 설계해야 하며, 주소 변경 시 주소 객체를 수정하지 않고 새 Address 값 객체로 교체한다.
| 비교 축 | A | B |
|---|---|---|
| 적합한 경우 | 고유 추적 필요 (Order, User) | 불변 개념 (Money, Address) |
| 부적합한 경우 | 속성값으로 충분한 개념 | 생명주기 추적이 필요한 개념 |
| 대표 예시 | Order, User, Product | Money, Address, DateRange |
- 📢 섹션 요약 비유: 도메인 개념이 엔티티인지 값 객체인지 불분명하면 비즈니스 전문가에게 '이것을 ID로 추적해야 합니까?'라고 질문한다.
Ⅳ. 실무 적용 및 기술사 판단
엔티티는 여권(ID 추적)처럼, 값 객체는 동전(속성값으로 동일성 결정)처럼 설계한다. 여권은 유효기간이 바뀌어도 동일 인물의 여권이지만, 동전은 동일 금액이면 모두 같다.
판단 체크리스트
- 식별자(ID)가 필요한 도메인 개념은 엔티티로, 속성값이 동일성을 결정하는 개념은 값 객체로 설계되어 있는가?
- 값 객체가 불변(Immutable)으로 구현되어 있으며 수정 시 새 객체로 교체하는가?
- Money, Address, DateRange 등 도메인 개념이 원시 타입(int, String) 대신 값 객체로 표현되어 있는가?
- 엔티티 간 참조가 직접 객체 참조 대신 ID 참조로 이루어지는가?
- 값 객체가 에그리게이트 내에서만 사용되고 에그리게이트 루트를 통해 접근되는가?
- 📢 섹션 요약 비유: 엔티티는 여권(ID 추적)처럼, 값 객체는 동전(속성값으로 동일성 결정)처럼 설계한다.
Ⅴ. 기대효과 및 결론
엔티티·값 객체 구분으로 도메인 모델의 의도가 코드에 명확히 표현되고, 불변 값 객체 덕분에 동시성 버그와 복사 부작용이 예방된다. 값 객체를 통한 Primitive Obsession(원시 타입 남용) 해소로 타입 안전성도 향상된다.
한계는 값 객체 설계 시 불변성 구현 비용과, 일부 ORM(JPA 등)에서 값 객체 영속화가 복잡할 수 있다는 점이다. 함수형 프로그래밍의 불변 자료구조와 값 객체의 융합이 미래 방향이다.
- 📢 섹션 요약 비유: 엔티티는 이름이 바뀌어도 동일인 취급(ID 추적), 값 객체는 같은 금액이면 동일 취급(속성값 비교). 이 두 가지 구분이 도메인 모델의 핵심이다.
📌 관련 개념 맵
[DDD 엔티티·값 객체] → [에그리게이트 설계] → [불변 Value Object] → [CQRS 읽기 모델 최적화]
| 개념 | 연결 포인트 |
|---|---|
| 에그리게이트 루트 | 엔티티를 포함하는 일관성 경계 |
| 유비쿼터스 언어 | 엔티티·값 객체의 이름 기준 |
| 리포지토리 | 엔티티(에그리게이트 루트)를 영속화 |
| Domain Primitive | 비즈니스 규칙을 포함한 값 객체 패턴 |
📈 관련 키워드 및 발전 흐름도
[프리미티브 타입 남용] → [값 객체·엔티티 구분(Evans)] → [불변 Value Object] → [Domain Primitive 패턴] → [함수형 불변 자료구조 융합]
👶 어린이를 위한 3줄 비유 설명
- 엔티티는 사람처럼 이름이 바뀌어도 주민번호로 구별해요.
- 값 객체는 동전처럼 금액이 같으면 어느 동전이든 똑같아요.
- 이 두 가지를 잘 구분하면 코드가 훨씬 명확해져요!