반응형
DDD(도메인 주도 설계)에서 Value Object(VO) 는 도메인 모델을 설계할 때 꼭 등장하는 개념입니다.
하지만 많은 개발자들이 “VO가 뭐야?” 혹은 “동등성으로 비교한다는 게 무슨 뜻이야?”라는 부분에서 헷갈려하죠.
오늘은 이 개념을 실제 코드와 함께 명확히 정리해보겠습니다.
Value Object란?
Value Object(VO) 는 고유한 ID가 없고,
그 값 자체로 동일성을 판단하는 객체입니다.
즉, 값이 같다면 다른 객체여도 “같은 것으로 본다”는 의미예요.
대표적인 예로는 Money(금액), Address(주소), Email(이메일) 같은 객체가 있습니다.
동일성과 동등성의 차이
자바에서는 객체를 비교할 때 두 가지 기준이 있습니다.
구분의미자바 비교 방식
| 동일성 (Identity) | 메모리 상에서 같은 객체인가? | == |
| 동등성 (Equality) | 내부 값이 같은가? | equals() |
예시로 이해하기
Money m1 = new Money(1000, "KRW");
Money m2 = new Money(1000, "KRW");
- m1 == m2 → 틀림 (메모리 주소가 다름)
- m1.equals(m2) → 같음 (값이 같음)
즉, VO는 값이 같으면 같은 객체로 본다는 원칙을 따릅니다.
그래서 VO에서는 항상 equals()와 hashCode()를 재정의해야 합니다.
예시 코드 — Money VO
public class Money {
private final int amount;
private final String currency;
public Money(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("통화 단위가 다릅니다.");
}
return new Money(this.amount + other.amount, currency);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return amount == money.amount &&
Objects.equals(currency, money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
}
특징 정리
- id가 없습니다.
- 값이 같으면 같은 객체로 취급합니다.
- 불변(Immutable) 객체로 설계합니다.
→ 한 번 생성되면 내부 값이 변하지 않습니다.
또 다른 예 — Address VO
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
protected Address() {} // JPA 기본 생성자
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Address)) return false;
Address address = (Address) o;
return Objects.equals(city, address.city) &&
Objects.equals(street, address.street) &&
Objects.equals(zipcode, address.zipcode);
}
@Override
public int hashCode() {
return Objects.hash(city, street, zipcode);
}
}
그리고 아래와같이 엔티티에서 포함시켜 사용합니다
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded
private Address address;
}
이 경우 Member는 Entity,
Address는 Value Object입니다.
Entity vs Value Object 비교
구분EntityValue Object
| 식별자(ID) | 있음 | 없음 |
| 비교 기준 | 동일성 (ID) | 동등성 (값) |
| 가변성 | 변경 가능 | 불변 |
| DB 매핑 | @Entity | @Embeddable |
| 예시 | 주문(Order), 회원(User) | 주소(Address), 금액(Money), 이메일(Email) |
VO를 설계할 때 주의할 점
- 항상 불변(Immutable) 으로 만들 것
→ setter 금지, 생성자에서만 값 설정 - equals() / hashCode() 반드시 재정의
→ 값 기반 비교를 위해 필요 - 비즈니스 규칙은 VO 안으로 응집
→ 예: Email 형식 검증, 금액 계산 등
정리
🔹 동일성(Identity) → 같은 객체인가? (==)
🔹 동등성(Equality) → 값이 같은가? (equals())
VO는 “값이 같으면 같은 객체로 본다.”
즉, 동등성으로 판단하는 객체다.
반면 Entity는 ID가 같으면 같은 객체로 본다.
즉, 동일성으로 판단하는 객체다.
마무리 한 줄
VO는 단순한 데이터 컨테이너가 아니라,
도메인의 의미를 담고, 불변성과 동등성을 보장하는 ‘작은 규칙의 단위’다.
반응형
'Spring' 카테고리의 다른 글
| 카프카(Kafka) 기본 개념 정리 – 컨슈머와 리스너는 어떻게 다를까? (0) | 2025.12.05 |
|---|---|
| [Spring Boot] JUnit5 + Mockito 단위 테스트 기초 정리 (0) | 2025.11.27 |
| [DDD] 자바에서의 참조 공유와 (Value Object, Entity) 정리 (0) | 2025.10.17 |
| Spring Security 기반 CSRF 공격 실습 및 방어 방법 정리 (1) | 2025.05.04 |
| [QueryDSL 오류]Springboot 3.0이상에서 QueryDslConfig Cannot resolve constructor 'JPAQueryFactory(EntityManager)' (0) | 2025.02.21 |