카테고리 없음

VO (Value Object) 가 뭐야 ?

1son 2026. 6. 3. 16:38

 

Value Object,

- 값이 곧 정체성인 객체. 값이 같으면 같은 것이고, 한번 만들면 바꿀 수 없다. 

- 값 자체가 의미인 객체 


풀어서 말하면, 

평소에 코드 짜다보면 이런거 씀 

String email = "hong@gmail.com";
int price = 10000;
String zipCode = "06134";

 

이게 그냥 문자열, 숫자로 떠돌아다니다 보면 두 가지 문제가 발생 

 

문제 1 — 검증 코드가 여기저기 흩어짐

// A 파일에서
if (email.contains("@")) { ... }

// B 파일에서도
if (email.contains("@") && email.length() > 5) { ... }

 

문제 2 — 타입이 같아서 실수해도 모름

void join(String email, String phone) { ... }

join("010-1234-5678", "hong@gmail.com"); // 바꿔 넣어도 오류 없음

 

 

VO가 이 두 문제를 해결하는 방식

// 검증 + 관련 기능을 객체 안에 캡슐화
public class Email {
    private final String value;  // 불변

    public Email(String value) {
        if (!value.contains("@")) throw new Exception("이메일 형식 오류");
        this.value = value;      // 여기서 한 번만 검증
    }
}

public class PhoneNumber {
    private final String value;

    public PhoneNumber(String value) {
        if (!value.startsWith("010")) throw new Exception("전화번호 형식 오류");
        this.value = value;
    }
}

// 이제 순서 바꾸면 컴파일 타임에 바로 잡힘
void join(Email email, PhoneNumber phone) { ... }

 

 


VO로 뭘 할 수 있냐? 

1. 검증을 한 곳에 모은다. -> 생성자에서 한 번만 검증하면, 이 객체를 받는 쪽은 이미 유효한 값임을 믿을 수 있음 

2. 타입으로 실수를 막는다. -> String 대신 email, PhoneNumber 타입을 쓰면 잘못된 값이 들어오는 걸 컴파일 타임에 차단 

 

언제 쓰냐? 

- 이 값에 대해 검증 로직이 있다 

- 같은 타입인데 의미가 다른게 있다 

 

반대로 

고유한 ID가 필요하다 -> Entity

 

 

한 줄 요약 

: 원시 타입 (String, int) 으로 떠돌던 값에 이름과 책임을 부여한 것 : VO 

타입 안정성 + 검증 증집 + 관련 기능 캡슐화, 이 세가지를 얻기 위해 씀 

 

 


 

쉽게 설명하면, 

핵심 비유 : 1만원 짜리 지폐 

지갑에서 1만원짜리 지폐 두 장을 꺼냈다고 생각

두 지폐의 일련번호는 다르지만, 우리는 둘 다 똑같이 1만원으로 취급함 

카페에서 커피를 살 때 이 지페가 아니면 안돼 ! 라고 하지 않음 

 

=> 값이 같으면 = 같은 것 으로 보는게 VO 

 


반대로, Entity 는 ? 

 

같은 비유로 사람을 생각해보면, 

"홍길동" 이라는 이름을 가진 사람이 둘 있어도, 둘은 다른 사람임. 

 이름 (값)이 같아도 각자의 고유한 정체성 (ID)가 있으니까 

지폐 (VO) -> 값이 같으면 같은 것 
사람 (entity) -> ID(주민번호) 가 다르면 다른 것

 


VO의 또 다른 특징 - "불변"

1만원 짜리 지폐는 그 자체로 완성된 값임 

1만원을 2만원으로 수정하는 건 불가능. 새 지폐를 만들어야함 

 

VO 도 똑같음. 한번 만들어진 VO는 값을 바꿀 수 없고 새로운 VO를 만들어야함 

기존 1만원을 2만원으로 수정  XXX 
기존 1만원 + 새 1만원 = 2만원 짜리 새 객체 생성 O

 

 

반면 주문, 회원, 상품은 내용이 같아도 다를 수 있으니 Entity임 

같은 상품이 두개 있어도, 재고 번호가 다르면 다른 상품이니까 

 


 

왜 쓰는가? 

1. 문제상황 

회원가입기능 만들기 

회원가입(이름, 이메일, 전화번호)

 

그런데 개발자가 실수로 순서를 바꿔서 호출함 

 

회원가입("홍길동", "010-1234-5678", "hong@gmail.com")
                      ↑ 이메일 자리에    ↑ 전화번호 자리에
                        전화번호 입력      이메일 입력

 

둘 다 그냥 문자열 (String) 이라서, 컴퓨터는 아무 오류도 안냄 

나중에 문자 발송 할 때 되서야 발견하게 됨 

 


VO를 쓰면 ? 

이메일은 Email 타입, 전화번호는 PhoneNumber 타입으로 만들어두면 

회원가입("홍길동", 전화번호, 이메일)
                    ↑
         "Email 자리에 PhoneNumber가 들어왔어요!" 즉시 오류 발생

코드를 실행하기 전에 실수를 잡아냄 

 

 

 

 

실제 쇼핑몰에서 사용하는 구현 예시 

쇼핑몰에 등장하는 VO 후보들 

주문 흐름: 회원가입 → 상품 검색 → 장바구니 → 결제 → 배송
              ↓           ↓          ↓         ↓       ↓
            Email       Price      Money    CardNumber  Address
          PhoneNumber

 

1. 회원가입 - Email, PhoneNumber

문제상황 

// ❌ VO 없이 - 잘못 입력해도 오류 안 남
User 회원가입("홍길동", "010-1234-5678", "hong@gmail.com");
//                       ↑ 이메일 자리   ↑ 전화번호 자리
//                         전화번호 입력    이메일 입력 → 오류 없음!

 

VO 적용 후 

// Email VO
public class Email {
    private final String value;

    public Email(String value) {
        if (!value.contains("@"))
            throw new Exception("이메일 형식이 아닙니다");  // 생성 시 바로 검증
        this.value = value;
    }
}

// PhoneNumber VO
public class PhoneNumber {
    private final String value;

    public PhoneNumber(String value) {
        if (!value.startsWith("010"))
            throw new Exception("전화번호 형식이 아닙니다");  // 생성 시 바로 검증
        this.value = value;
    }
}

// ✅ VO 적용 - 순서 바꾸면 즉시 오류
User 회원가입("홍길동", new Email("hong@gmail.com"), new PhoneNumber("010-1234-5678"));

 

 

핵심 정리 

 

VO는 "관련된 것들을 한 곳에 모아서, 실수를 줄이고 수정을 쉽게 " 만드는 도구