VO vs DTO (with record)

한강섭·2025년 4월 3일
1
post-thumbnail

썸네일 출처


VO (Value Object)

VO (Value Object)는 값(Value)을 표현하는 객체로, 동일한 값을 가지는 두 객체는 동일한 것으로 간주됩니다. VO는 주로 불변(immutable) 객체로 설계되며, 값의 동일성을 비교할 때 사용됩니다.

  1. ‘값’ 자체를 표현하는 객체
  2. equals()와 hashCode()를 오버라이딩해서 모든 속성 값이 같다면 같은 객체로 취급한다.
  3. 불변성을 보장하는 객체
  4. 로직을 포함할 수 있는 객체

왜 사용할까?

대표 예시

  1. Money : 금액을 표현하고, 연산 메서드 포함
  2. Email : 이메일 형식을 검증하여 안전한 이메일 객체 생성
  3. PhoneNumber : 전화번호 형식을 보장하여 데이터 안정성 유지
  4. Address : 주소 정보를 하나의 객체로 묶어 관리
  5. Coordinate : 위도 경도 값을 하나의 객체로 관리

예시코드 (Email)

import java.util.Objects;

public class Email {
    private final String email;

    public Email(String email) {
        if (!email.matches("^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,4}$")) {
            throw new IllegalArgumentException("올바른 이메일 형식이 아닙니다.");
        }
        this.email = email;
    }

    public String getEmail() {
        return email;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Email email1 = (Email) o;
        return Objects.equals(email, email1.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(email);
    }
}
Email email = new Email("test@example.com");
System.out.println(email.getEmail()); // test@example.com

DTO

DTO (Data Transfer Object)는 계층 간 데이터를 전송하기 위한 객체입니다. Database에서 조회한 데이터를 서비스 계층으로 전달하거나, 클라이언트로부터 받은 데이터를 서비스 계층에 전달할 때 사용됩니다.

DTO는 VO와는 달리 getter/setter를 포함할 수 있다.

대신 이 외의 비즈니스 로직은 포함하지 않는다. 목적 그대로 "데이터를 전달" 하기 위해 사용되는 객체이다.


예시코드

public class PrizeDto {
    private String prizeName;

    public PrizeDto(String prizeName) {
        this.prizeName = prizeName;
    }

    public String getPrizeName() {
        return prizeName;
    }

    public void setPrizeName(String prizeName) {
        this.prizeName = prizeName;
    }
}

recode

필드 유형과 이름만 필요한 불변 데이터 클래스이다.

필드를 기반으로 자동으로 생성자, equals, hashCode, toString, getter 등을 제공한다.

덕분에 중복 코드를 줄여 코드를 간결하게 만들 수 있다.

record의 필드는 final로 선언되며 생성자에서 초기화되면 수정할 수 없다. 따라서 외부에서의 값 변경을 막아 안정적인 데이터 전달이 가능해진다.


사용 예시

@Builder
public record Person(
        String firstName,
        String lastName,
        int age
) {
}

class 대신 record 써주면 끝..


결론

현업에서는 VO와 DTO를 명확히 구분하지 않고 사용하는 경우가 많습니다. 실무에서는 코드의 간결성과 생산성을 중시하기 때문에, 이론적으로 구분되는 개념들이 실질적으로는 혼용되는 경우가 많습니다.

그러나 시스템이 복잡해질수록, 특히 도메인 주도 설계(DDD)를 채택하는 프로젝트에서는 VO와 DTO의 구분이 중요해질 수 있습니다. 그리고 VO나 DTO 를 Record로 사용하는 것이 간결한 코드작성과 불변성을 보장하는 측면에서 유리할 수 있습니다. 하지만 Java 14 이상이어야 하고, 상속을 받지 못하는 단점도 존재합니다.

결국, 상황에 따라 유연하게 접근하는 것이 중요합니다. 기본 정책은 Record 를 사용하고, 복잡한 로직이나 상속 구조가 필요할 때는 전통적인 클래스를 사용하는 것이 적절할 수 있습니다.


찐 결론

VO는 동등성 비교가 핵심! (비즈니스 로직 O)

DTO는 계층 간 데이터 전달이 핵심!

Record는 DTO를 편하게 하기 위한 Syntactic Sugar!


참고한 자료

VO, DTO, Record 뭘 써야 할까?
[JAVA] VO(Value Object) vs DTO(Data Transfer Object) with record
VO(Value Ojbect)란 무엇일까?
VO와 DTO 그리고 VO로 쓰기 딱 좋은 record


profile
개발😊

0개의 댓글