VO (Value Object)와 DTO (Data Transfer Object)는 모두 Java 및 Spring과 같은 객체 지향 프로그래밍 및 프레임워크에서 데이터를 표현하고 전달하는 데 사용되는 설계 패턴이다.
불변성(Immutability)
VO(Value Object)
VO는 불변 객체이다. 즉, 일단 생성된 후에는 그 상태를 변경할 수 없다.
VO는 일반적으로 속성을 가진 작은 객체로, 이들 속성은 객체가 생성될 때 설정되고 그 후에는 변경되지 않는다.
불변성은 VO의 중요한 특징 중 하나이며, 이는 복잡성을 줄이고 프로그램의 안정성을 향상시킨다.
DTO(Data Transfer Object)
DTO는 원칙적으로 불변성을 가져야하지만, 실제 사용시에는 상황에 따라 변경 가능성을 가질 수도 있다.
DTO는 다른 계층 또는 서비스간의 통신에서 사용되는 객체로, 그 목적은 단순히 데이터를 한 데 묶어 전달하는 것이다. DTO가 변경 가능한 객체인지 불변한 객체인지는 그 사용 방법에 따라 달라질 수 있다. 일부 경우에는, 데이터를 생성한 후에 추가적인 변형이나 가공을 위해 DTO를 변경해야 할 수도 있다.
그러나 원칙적으로 DTO는 불변성을 가지는 것이 좋다. DTO는 데이터 전송의 목적으로 사용되기 때문에, 데이터 전송이 완료된 이후에는 원래의 상태가 유지되는 것이 일반적이다. 이렇게 함으로써, 데이터의 일관성이 유지되고 버그의 가능성이 줄어들기 때문이다.
VO(Value Object)의 동등성
VO 동등성 예시
public class Money {
private final int amount;
private final String currency;
public Money(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Money money = (Money) obj;
return amount == money.amount && currency.equals(money.currency);
}
// hashCode() 메서드도 재정의
}
Money money1 = new Money(100, "USD");
Money money2 = new Money(100, "USD");
System.out.println(money1.equals(money2)); // true
- DTO(Data Transfer Object)의 동등성
DTO 동등성 예시
public class UserDTO {
private Long id;
private String name;
private String email;
// getters and setters
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
UserDTO userDTO = (UserDTO) obj;
return id.equals(userDTO.id);
}
// hashCode() 메서드도 재정의
}
UserDTO user1 = new UserDTO();
user1.setId(1L);
user1.setName("Alice");
user1.setEmail("alice@example.com");
UserDTO user2 = new UserDTO();
user2.setId(1L);
user2.setName("Bob");
user2.setEmail("bob@example.com");
System.out.println(user1.equals(user2)); // true
VO와 DTO의 동등성 정리
VO
모든 속성 값이 같아야 동등하다고 판단된다. 값 자체에 의해 정의되므로 "Value Object"라고 불린다.
DTO
식별자나 핵심 속성이 같으면 동등하다고 판단된다. 데이터베이스 레코드를 표현하는 경우가 많으므로, 고유한 ID를 기준으로 판단하는 경우가 일반적이다.
VO(Value Object)
VO예시: Money class
public class Money {
private final int amount;
private final String currency;
public Money(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
// getters, equals, hashCode
}
DTO(Data Transfer Object)
DTO예시: UserDTO class
목적: 서버와 클라이언트 간의 통신이나 데이터베이스의 레코드를 가져오는 등의 작업에 사용된다. 예를 들어, REST API의 응답으로 사용자 정보를 전달하는 데 사용할 수 있다.
특징: 일반적으로 식별자나 핵심 속성에 의해 동등성이 결정되며, 다른 계층간의 데이터 전송을 위해 설계된다.
@Getter
@Setter
public class UserDTO {
private Long id;
private String name;
private String email;
}
사용 목적 정리
VO: 의미 있는 비즈니스 개념을 표현하며, 불변성을 갖고, 비즈니스 로직에서 사용된다.
DTO: 계층 간 또는 시스템 간의 데이터 전송을 위한 구조로, 순수한 데이터 컨테이너 역할을 한다.
VO (Value Object)는 '화폐'와 같다.
1000원 짜리 지폐를 생각해보자. 이 지폐는 1000원의 가치를 나타내고, 그 가치는 변하지 않는다. 다른 1000원 짜리 지폐와 비교해도 가치는 동일하다.
지폐의 색깔이나 크기, 무게가 달라도 1000원의 가치는 변하지 않는 것처럼, VO는 그 값을 나타내는 속성이 같다면 동일한 객체로 취급된다.
DTO (Data Transfer Object)는 '학생의 성적표'와 유사하다.
학생의 성적표에는 이름, 과목별 점수 등 여러 정보가 들어있다. 성적표는 학기가 끝날 때마다 업데이트 될 수 있다.
성적표의 내용이 바뀌더라도, 같은 학생의 성적표라면 동일한 성적표로 취급된다. 성적이 변하더라도, 그 성적표는 같은 학생의 것이기 때문이다.
결론
Entity 클래스
DB의 테이블을 객체로 표현하며, 각 테이블의 행은 Entity 클래스의 인스턴스에 매핑된다. Entity 클래스의 인스턴스는 데이터베이스에서 가져온 데이터를 나타내므로 변경될 수 있다. 또한 비즈니스 로직을 포함할 수 있으며, 이 점은 DTO와 차이가 있다.
DTO (Data Transfer Object)
여러 계층 또는 시스템 간에 데이터를 전송하는 데 사용된다. Entity 클래스의 인스턴스는 그대로 클라이언트에게 전달되지 않는다. 대신, 필요한 데이터만 DTO에 담아서 전달한다. 데이터베이스 구조의 세부 사항을 숨기고, 필요한 데이터만 제공하는 역할을 한다.
VO (Value Object)
JPA에서도 활용될 수 있으며, 엔티티의 일부분을 나타내는 데 사용될 수 있다. 불변성을 가질 수 있으며, 특정 비즈니스 개념을 표현한다.
결론
JPA 환경에서는 Entity 클래스가 데이터베이스와 직접 매핑되어 데이터를 표현하며, DTO는 클라이언트와 서버 간의 데이터 전송을 담당한다.
VO는 특정 비즈니스 개념을 나타내는 데 사용되며, 엔티티의 일부로 활용될 수 있다.
이 세 가지 개념은 각각의 역할과 책임에 따라 적절하게 사용되어야 하며, 이를 통해 코드의 유연성과 유지보수성을 높일 수 있다.
DTO (Data Transfer Object)의 주요 사용 상황
ORM(Object-Relational Mapping) 또는 SQL 매퍼 프레임워크:
단순 CRUD 작업:
클라이언트와 서버 간의 통신:
VO (Value Object)의 주요 사용 상황
도메인 주도 설계 (Domain Driven Design, DDD):
복잡한 도메인 모델링:
불변성 요구:
결론
DTO:
VO:
적절한 선택: