DTO, VO

Jerry·2025년 7월 31일

DTO (Data Transfer Object)란?

DTO는 시스템 내부 또는 외부 계층 간에 데이터를 효율적이고 안전하게 전달하기 위한 객체입니다.
주로 계층 간의 경계(boundary)에서 사용되며, 비즈니스 로직을 포함하지 않고 순수 데이터만을 담는 객체입니다

주요 특징

항목설명
역할데이터 캡슐화 및 전달
포함 내용필드 + Getter/Setter, toString(), equals(), hashCode() 등
비즈니스 로직❌ 없음 (단순 데이터 구조체)
불변성(Immutable)가능하면 적용 권장 (record, final 등)
계층 간 전달Controller ↔ Service ↔ Repository 등에서 사용

Java Class로 DTO 정의

public class UserDTO {
    private String name;
    private String email;

    public UserDTO() {} // 기본 생성자 (필수: JSON 역직렬화 등)

    public UserDTO(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() { return name; }
    public String getEmail() { return email; }

    public void setName(String name) { this.name = name; }
    public void setEmail(String email) { this.email = email; }
}

Java 16+ record로 불변 DTO 정의

public record UserDTO(String name, String email) {}
  • final, getter, 생성자, toString, equals, hashCode 자동 생성
  • DTO에 가장 적합한 구조 중 하나

사용 예시 (Spring MVC 기반)

Controller → Service로 전달

@PostMapping("/users")
public ResponseEntity<Void> createUser(@RequestBody UserDTO userDto) {
    userService.create(userDto);
    return ResponseEntity.ok().build();
}

사용 이유

이유설명
보안Entity 전체를 외부에 노출하면 민감 정보 유출 가능 (ex. 비밀번호, 내부 ID 등)
API 명세 분리클라이언트가 원하는 필드 구조로 구성 가능 (Entity와 무관하게)
계층 분리Controller, Service, Repository 간의 책임 분리
성능 최적화필요한 필드만 전송하여 네트워크 비용 절감

잘못된 사용 예시

// ❌ Entity를 직접 API 응답으로 반환
@GetMapping("/users")
public List<User> getUsers() {
    return userRepository.findAll(); // Entity 노출 위험!
}

→ ✅ DTO로 변환 후 반환해야 함.

정리

항목요약
DTO란?데이터 전달 전용 객체 (계층/시스템 간 전송 목적)
포함 내용필드, getter/setter, toString 등 (비즈니스 로직 없음)
사용 목적보안, 성능, API 명세 분리, 계층 분리
추천 방식Java 16+에서는 record 사용으로 불변 DTO 작성 권장

VO (Value Object)란?

VO는 객체의 고유 식별자(ID)보다는 속성 값 자체가 객체의 동일성을 결정하는 객체입니다.
주로 작고 변경되지 않는 값(불변 객체)을 표현하며, 도메인 모델링에서 핵심적인 개념입니다.

주요 특징

항목설명
역할값 자체로 동일성을 판단하는 객체
식별자❌ 없음 (ID가 아니라 으로 동등성 비교)
불변성✅ 필수 (값이 변하면 객체 자체가 달라진 것으로 간주)
비즈니스 로직✅ 포함 가능 (자기 완결적 연산)
사용 위치도메인 모델, JPA 임베디드 타입, 계산 결과, 설정값 등

Java Class로 VO 구현

public final class Email {
    private final String value;

    public Email(String value) {
        if (!value.matches("^[\\w._%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$")) {
            throw new IllegalArgumentException("Invalid email format");
        }
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    // equals, hashCode는 값 기준으로 반드시 오버라이드
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Email)) return false;
        Email email = (Email) o;
        return value.equals(email.value);
    }

    @Override
    public int hashCode() {
        return value.hashCode();
    }

    @Override
    public String toString() {
        return value;
    }
}

Java 16+ record로 불변 VO

public record Money(int amount, String currency) {
    public Money {
        if (amount < 0) throw new IllegalArgumentException("Amount must be non-negative");
    }
}
  • record는 VO에 매우 적합: 자동 final, equals, hashCode, toString 생성

실무 예시 (Address VO)

public record Address(String street, String city, String zip) {
    public Address {
        if (zip.length() != 5) throw new IllegalArgumentException("Invalid zip code");
    }
}

Order 엔티티에 포함되는 임베디드 값 객체로 사용

사용 이유

이유설명
불변성 보장참조 공유로 인한 의도치 않은 변경 방지
도메인 안정성Email, Money, Address 등 의미 있는 값으로 모델링 가능
equals/hashCode 기반 동일성 판단값이 같으면 같은 객체로 취급 (ID 불필요)
캡슐화와 검증유효성 검사를 생성자나 팩토리에서 처리 가능

주의 사항

항목설명
equals/hashCode 반드시 overrideVO의 핵심: 값이 같으면 같은 객체
setter 절대 금지불변 객체 원칙 위반
JPA에서는 @Embeddable 활용VO를 Entity 내부에 내장 타입으로 사용 가능

정리

항목요약
VO란?값 자체로 객체를 정의하는 불변 객체
불변성필수 (생성 후 값 변경 불가)
동일성식별자(X) → 값 기반 equals/hashCode
사용 예시이메일, 금액, 주소, 전화번호 등
추천 구현final class, record, 생성자 검증, equals/hashCode 구현

DTO vs VO

항목DTO (Data Transfer Object)VO (Value Object)
목적계층/시스템 간 데이터 전달값 자체의 의미 표현 및 불변성 유지
동일성 기준동일성 없음 (그저 데이터 컨테이너)필드 값(value) 기반 equals()/hashCode()
불변성선택 사항 (보통 가변)필수 (불변 객체로 설계)
포함 정보순수 데이터 (getter/setter만)도메인에서 의미 있는 값 + 간단한 로직
비즈니스 로직없음 (비즈니스 로직 금지)간단한 유효성 검증/계산 포함 가능
캡슐화주로 단순 구조 (필드 노출)내부 필드 보호, 유효성 검사 포함
사용 위치Controller ↔ Service ↔ Repository 사이도메인 모델 내부 (Entity 필드 등)
직렬화 목적직렬화 대상 (JSON/XML)보통 사용하지 않음
JPA 사용Entity로 직접 매핑하지 않음@Embeddable로 내장 사용 가능
Setter 제공보통 있음 (가변 구조)없음 (불변 원칙)
적합한 구현 방식일반 class, 가끔 recordfinal class 또는 record
  • VO 신분증처럼 값 자체가 의미 있고 고유한 구조체 (이메일, 돈, 주소 등)
  • DTO 택배 상자처럼 데이터를 옮기기 위한 포장재 (값을 담기만 함)
profile
Backend engineer

0개의 댓글