[Spring] DTO & VO

thezz9·2025년 3월 28일

개요

DTO와 VO의 개념은 Java와 Spring 애플리케이션에서 자주 혼동되는 부분이다. 나 또한 두 객체의 차이점을 구별하기 힘들어 이 글을 작성하면서 공부해보려고 한다. 여러 자료를 찾아보니 나처럼 헷갈려하는 사람들이 많더라..

더 이상 헷갈려하지 않기 위해 DTO(Data Transfer Object)VO(Value Object)의 차이를 명확히 구분하고, 각각이 어떻게 사용되는지에 대해 정리해보려고 한다.


1. DTO (Data Transfer Object)

DTO는 계층 간 데이터를 전달하기 위한 객체
Spring 애플리케이션에서 주로 Controller ↔ Service ↔ Repository 간에 데이터를 주고받을 때 사용

DTO 특징

  • 데이터 전달용 객체로, 서비스 계층 또는 컨트롤러에서 필요한 데이터를 필드로 담아서 전달.
  • 가변 객체로 설계될 수 있으며, 직렬화(Serialization)가 가능.
  • 주로 HTTP 요청/응답에 사용되며, API 응답을 위한 JSON 형태로 변환될 수 있음.
  • Spring의 @RestController에서 API 응답으로 활용하거나, @RequestBody로 API 요청을 처리하는 데 사용됨.

DTO 예시 (Spring Controller 사용)

public class UserDTO {
    private String name;
    private int age;

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

    // Getter
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO userDTO) {
        // DTO를 받아서 처리 후, 다시 DTO로 응답
        return ResponseEntity.ok(userDTO);
    }
}

DTOHTTP 요청을 처리할 때 @RequestBody로 요청 데이터를 받거나, @ResponseBody로 JSON 응답을 보낼 때 사용된다.

DTO 사용 목적

  • 계층 간 데이터 전달 (Service ↔ Controller 등)
  • HTTP 요청/응답 처리 (주로 API 데이터 전송)
  • 필요한 데이터만 포함, 불필요한 데이터 제외 가능

(💡+추가) DTO를 불변 객체로 선언하는 방법과 장점

DTO를 불변 객체로 설계하면, 객체가 생성된 후 데이터 변경을 방지할 수 있어 데이터의 무결성을 보장한다.
final 필드와 생성자를 사용하여 불변 객체를 선언할 수 있다.

DTO 불변 객체 예시

public class UserDTO {
    private final String name;
    private final int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

불변 DTO의 장점

  1. 무결성 유지: 객체의 상태가 변경되지 않기 때문에 데이터의 무결성을 보장.
  2. 스레드 안전성: 불변 객체는 멀티스레드 환경에서도 안전하게 사용될 수 있음.
  3. 의도 명확화: 불변 객체를 사용하면 값 변경을 방지할 수 있어, 다른 개발자에게 의도를 명확히 전달할 수 있음.

2. VO (Value Object)

VO는 값 객체로, 에 의미를 두고 불변(Immutable)하도록 설계
Spring에서 VO는 주로 비즈니스 로직에서 값을 표현하는 객체로 사용

VO 특징

  • 불변(Immutable) 객체로 설계되며, 값 객체의 동등성은 값으로 판단함.
  • 동일한 값이면 동일한 객체로 취급되며, 값이 변경될 수 없도록 설계함.
  • Spring JPA에서 @Embeddable로 다른 엔티티에 포함되거나, @Value 어노테이션으로 다룰 수 있음.
  • 엔티티(Entity)와 달리 식별자(ID)가 없고, 값 자체가 중요.

VO 예시 (Spring JPA에서 사용)

@Embeddable
public class Address {
    private final String city;
    private final String street;

    protected Address() {}  // JPA에서 VO를 사용할 때 기본 생성자 필요

    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }

    public String getCity() {
        return city;
    }

    public String getStreet() {
        return street;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Address address = (Address) obj;
        return Objects.equals(city, address.city) &&
               Objects.equals(street, address.street);
    }

    @Override
    public int hashCode() {
        return Objects.hash(city, street);
    }
}
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Embedded
    private Address address;  // VO 사용

    public User(String name, Address address) {
        this.name = name;
        this.address = address;
    }
}

VO비즈니스 로직에서 값 객체를 표현하는 데 사용되며, 값이 동일하면 동일한 객체로 취급된다.

VO 사용 목적

  • 불변 값을 표현하는 객체로 사용 (ex. Address, Money 등)
  • Entity의 일부로 포함되어 값을 의미 있게 관리
  • 값이 동일하면 동일한 객체로 취급

3. DTO와 VO의 차이점 (Spring 기준)

구분DTO (Data Transfer Object)VO (Value Object)
주요 목적계층 간 데이터 전달값 객체 표현
불변성가변/불변 가능불변 (Immutable)
동등성 판단객체 참조 비교 (ID 기준)값 비교 (equals()hashCode() 기준)
식별자없음 (주로 전달용)없음 (값 자체가 중요)
Spring에서 사용요청/응답 데이터 처리엔티티의 일부로 사용 (값 객체)
저장 여부데이터베이스 저장 안 함엔티티 내부에서 사용, DB에 저장되지 않음

4. DTO와 VO 사용 예시 (Spring 애플리케이션)

1️⃣ DTO 예시 (API 요청/응답 처리)

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @PostMapping
    public ResponseEntity<OrderDTO> createOrder(@RequestBody OrderDTO orderDTO) {
        // OrderDTO를 받아서 주문 생성 로직 처리 후 응답
        return ResponseEntity.ok(orderDTO);
    }
}

OrderDTOAPI 요청/응답을 처리하는 데 사용된다. @RequestBody로 요청을 받고, @ResponseBody로 응답을 보낸다.

2️⃣ VO 예시 (비즈니스 로직에서 사용)

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Embedded
    private Money totalPrice;  // VO 사용

    public Order(Money totalPrice) {
        this.totalPrice = totalPrice;
    }
}

@Embeddable
public class Money {
    private final int amount;

    public Money(int amount) {
        this.amount = amount;
    }

    public int getAmount() {
        return amount;
    }
}

MoneyVOOrder 엔티티의 일부로 @Embedded 어노테이션을 통해 값 객체로 사용되며, 엔티티의 비즈니스 로직을 깔끔하게 처리한다.


5. DTO와 VO를 언제 사용하면 좋을까?

사용처DTOVO
Controller ↔ Service✅ 사용 (요청/응답)❌ 사용 안 함
Service ↔ Repository✅ 사용 가능❌ 잘 사용 안 함
도메인 모델 (Entity 내부)❌ 사용 안 함✅ 사용 (값 객체)
API 응답용 데이터✅ 사용❌ 사용 안 함

결론

DTOHTTP 요청/응답 처리와 계층 간 데이터 전달을 위한 객체로, 가변일 수 있지만 불변으로 설계하는 것이 좋다.

  • DTO는 서비스 계층과 컨트롤러 간에 데이터를 전송하는 데 사용되며, @RequestBody@ResponseBody와 함께 JSON 형식으로 데이터를 주고받을 때 유용하다.
  • 불변 DTO를 사용하면 데이터 변경을 방지하고, 데이터의 무결성을 보장할 수 있다. 멀티스레드 환경에서도 안정적으로 사용할 수 있으며, 객체가 불변임으로써 개발자의 의도를 명확히 전달할 수 있다.

VO불변 객체로 설계되어 값을 표현하는 데 사용되며, 동일한 값이면 동일한 객체로 취급한다.

  • VO는 주로 비즈니스 로직에서 값의 의미를 표현하는 데 사용된다. 예를 들어, AddressMoney와 같은 값 객체는 불변해야 하며, 값이 동일하면 동일한 객체로 취급된다.
  • VO@Embeddable 어노테이션을 통해 엔티티에 포함될 수 있으며, 값 객체가 변경되지 않도록 보장된다. 또한, 값을 비교하는 equals()hashCode() 메서드를 통해 동등성을 판단한다.

DTO는 계층 간 데이터 전송 용도, VO는 도메인 모델의 값 표현 용도

  • DTO는 계층 간 데이터 전달을 위해 주로 사용되며, API 요청 및 응답 처리에 적합하다. 불필요한 데이터는 제외하고 필요한 정보만 전달하는 방식으로, 데이터 처리 과정에서 편리하게 사용될 수 있다.
  • VO는 도메인 모델 내에서 중요한 값들을 표현하며, 불변성을 유지하면서 데이터를 처리할 때 주로 사용된다. VO는 값 객체로, 변경되지 않는 값들을 안전하게 다룰 수 있다.
profile
개발 취준생

0개의 댓글