Spring

[DDD] Value Object의 동일성과 동등성 완벽 이해하기 Value Object의 동일성과 동등성 완벽 이해하기

곽코딩루카 2025. 10. 22. 11:21
반응형

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를 설계할 때 주의할 점

  1. 항상 불변(Immutable) 으로 만들 것
    → setter 금지, 생성자에서만 값 설정
  2. equals() / hashCode() 반드시 재정의
    → 값 기반 비교를 위해 필요
  3. 비즈니스 규칙은 VO 안으로 응집
    → 예: Email 형식 검증, 금액 계산 등

 

 

 정리

 

🔹 동일성(Identity) → 같은 객체인가? (==)

🔹 동등성(Equality) → 값이 같은가? (equals())

VO는 “값이 같으면 같은 객체로 본다.”
즉, 동등성으로 판단하는 객체다.

반면 Entity는 ID가 같으면 같은 객체로 본다.
즉, 동일성으로 판단하는 객체다.

 

 마무리 한 줄

 

VO는 단순한 데이터 컨테이너가 아니라,

도메인의 의미를 담고, 불변성과 동등성을 보장하는 ‘작은 규칙의 단위’다.

반응형