1. VO(Value Object)란?

VO(Value Object)는 값을 표현하는 객체로, 변경이 불가능(Immutable)하며 동일한 속성을 가지면 같은 객체로 간주됩니다.

VO의 주요 특징

  • 불변성(Immutable) → 객체 생성 후 값 변경 불가
  • 동등성(Equality) 비교 → 동일한 값을 가지면 같은 객체로 간주
  • 로직을 포함할 수 있음 → VO 내부에서 관련된 비즈니스 로직을 처리 가능

JPA에서 VO를 활용하면 엔티티의 일관성을 유지하고, 중복된 값 로직을 제거할 수 있는 장점이 있습니다.


2. JPA에서 VO 패턴 적용 방법

(1) VO를 활용하지 않은 일반적인 엔티티 설계

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String city;
    private String street;
    private String zipcode;
}

위처럼 엔티티 내에 주소 관련 필드를 직접 추가하면, 여러 엔티티에서 동일한 값 관련 로직이 중복될 수 있습니다.

(2) VO를 적용한 엔티티 설계

JPA에서는 @Embeddable@Embedded를 사용하여 VO 패턴을 적용할 수 있습니다.

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;
    
    protected Address() {} // 기본 생성자 필요

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}
@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    
    @Embedded
    private Address address;
}

VO 패턴 적용의 장점

재사용성 증가 → 여러 엔티티에서 동일한 값 객체를 재사용 가능

데이터 일관성 유지 → 동일한 값을 가지면 같은 객체로 관리

불변성 보장 → 값 변경이 불가능하여 안전한 데이터 모델링 가능


3. VO의 불변성 유지하기

VO는 값이 변경되면 안 되므로, Setter를 제공하지 않고, 생성자로만 값을 설정해야 합니다.

@Embeddable
public class Money {
    private int amount;

    protected Money() {}

    public Money(int amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("금액은 0 이상이어야 합니다.");
        }
        this.amount = amount;
    }
}

이렇게 하면 Money 객체가 생성된 이후 값을 변경할 수 없으며, 잘못된 값이 들어가는 것도 방지할 수 있습니다.


4. VO를 활용한 비즈니스 로직 처리

VO 내부에 연산 로직을 추가하면 비즈니스 규칙을 보다 직관적으로 관리할 수 있습니다.

@Embeddable
public class DiscountRate {
    private double rate;
    
    protected DiscountRate() {}
    
    public DiscountRate(double rate) {
        if (rate < 0 || rate > 100) {
            throw new IllegalArgumentException("할인율은 0~100% 사이여야 합니다.");
        }
        this.rate = rate;
    }
    
    public double applyDiscount(double price) {
        return price * (1 - rate / 100);
    }
}

이제 엔티티에서 applyDiscount() 메서드를 사용하여 할인된 가격을 쉽게 계산할 수 있습니다.

@Entity
public class Product {
    @Id @GeneratedValue
    private Long id;
    
    private double price;
    
    @Embedded
    private DiscountRate discountRate;
    
    public double getDiscountedPrice() {
        return discountRate.applyDiscount(price);
    }
}
 

+ Recent posts