흔히 DTO와 VO를 혼용해서 많이들 사용한다.
그렇다면 왜 혼용을 하게 됐을까?
core J2EE Patterns 책의 1판에서는 getter/setter가 있고 데이터 전송을 위해 사용되는 객체를 VO로 정의했지만, 2판부터는 TO로 재정의하였기 때문으로 추측된다.
비록 혼용되었고, 아직까지도 혼용되고 있지만 이름이 다르다는 것은 의미 역시 다르다는 것.
각각에 대해 명확한 사실을 알아보자.
DTO (Data Transfer Object)
목적 : 다른 레이어, 서비스, 혹은 외부 시스템 간의 데이터를 전송하기 위한 객체다.
사용 케이스 : 주로 데이터베이스와의 소통, 네트워크를 통한 데이터 전송, 클라이언트와 서버 간의 데이터 교환 등에 사용된다.
특징 : 데이터 전송을 목적으로 하기 때문에, 복잡한 비즈니스 로직을 포함하지 않는, 단순히 데이터를 담기 위한 용도로 사용된다. 대신, 다양한 데이터 필드와 그에 대한 getter/setter 메서드를 포함할 수 있다.
VO (Value Object)
목적 : 값 자체를 표현하기 위한 객체다. 불변의 값을 표현하는 객체이며, 주로 도메인 모델 내에서 데이터의 특성을 나타내기 위해 사용된다.
사용 케이스 : 도메인 모델 내부에서의 데이터 표현, 비즈니스 로직의 일부로서의 데이터 처리 등에 사용된다. 예를 들어, 금액, 좌표, 날짜 등과 같이 값의 동등성이 중요한 경우에 VO를 사용한다.
특징 : VO는 불변성을 가지며, 객체의 동등성 비교에 중점을 둔다. 따라서, VO는 일반적으로 변경 가능한 상태를 가지지 않으며, 생성 시점에 모든 필드 값을 초기화하고 이후에는 변경할 수 없다. 특정한 비즈니스 로직을 가질 수 있으며 setter 메서드를 제공하지 않는다. 값의 동등성을 비교하기 위해 equals()와 hashCode() 메서드를 오버라이드하며, 데이터의 불변성을 보장하기 위해 주로 final 키워드를 사용하여 선언된다.
표로 간단하게 정리하면 아래와 같다.
DTO | VO | |
용도 | 레이어 간 데이터 전달 | 걊 자체 표현 |
동등 결정 | 속성값이 모두 같다고 해서 같은 객체가 아니다. | 속성값이 모두 같으면 같은 객체다. |
가변 / 불변 | setter 존재 시 가변, setter 비 존재 시 불변 | 불변 |
로직 | getter/setter 외의 로직을 갖지 않는다. | setter 외의 로직을 가질 수 있다. |
두 객체는 값을 가진다는 객체의 기본적인 공통점이 있지만, 사용 목적과 로직에서 많은 차이를 보이고 있다. 또한 DTO는 메서드 호출 횟수를 줄이기 위해 데이터를 담고 있는 것이고, VO는 값이 같으면 동일한 오브젝트라고 볼 수 있는 특징을 보인다.
예를 들자면 아래와 같다.
DTO a = new DTO(1);
DTO b = new DTO(1);
a != b;
VO a = VO(1);
VO b = VO(1);
a == b;
아래는 DTO와 VO의 예시 코드이다.
DTO
public class UserDto {
private String name;
private String email;
// 기본 생성자
public UserDto() {
}
// 매개변수가 있는 생성자
public UserDto(String name, String email) {
this.name = name;
this.email = email;
}
// Getter와 Setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
VO
public class MoneyVo {
private final int amount;
private final String currency;
// 매개변수가 있는 생성자
public MoneyVo(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
// Getter만 제공
public int getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
// equals()와 hashCode()를 오버라이드하여 값의 동등성을 비교
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MoneyVo moneyVo = (MoneyVo) o;
return amount == moneyVo.amount && Objects.equals(currency, moneyVo.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
// 특정 비즈니스 로직
...
}
그러나 실제 개발 환경에서는 VO가 레이어 간 데이터 전송에 사용되는 경우도 있다. 이는 다음과 같은 상황에서 발생할 수 있다.
도메인 중심 설계 : 도메인 중심 설계(Domain-Driven Design, DDD)에서는 도메인 모델이 애플리케이션의 핵심을 이루며, VO는 이러한 도메인 모델을 표현하는 데 중요한 역할을 한다. 이 경우, VO는 다른 레이어로 전달되어도 그 의미와 불변성을 유지할 수 있다.
불변 데이터 전송의 필요성 : 레이어 간에 불변의 데이터를 전송해야 하는 경우, VO를 사용하여 이러한 불변성을 보장할 수 있다. 예를 들어, 특정 도메인 객체의 상태를 다른 레이어에 전달할 때, VO를 사용하면 데이터의 무결성을 유지할 수 있다.
통합 레이어 간의 일관성 : 애플리케이션의 다양한 레이어 간에 일관된 도메인 모델을 유지하고자 할 때, VO는 이러한 일관성을 보장하는 데 도움이 될 수 있다. VO를 통해 정의된 도메인 규칙이나 값의 형식을 애플리케이션의 모든 레이어에서 동일하게 사용할 수 있다.
하지만, 레이어 간 데이터 전송의 주된 목적으로는 DTO(Data Transfer Object)가 더 적합하다. DTO는 데이터 전송을 위해 설계되었으며, 다양한 레이어 간의 데이터 교환을 효율적으로 처리하기 위한 구조와 메서드를 제공한다. 또한 불변성에 관해서는 DTO 역시 setter 메서드를 제거함으로서 불변성을 보장할 수 있다.
결론적으로, 위와 같은, 혹은 위의 상황 외에도 VO가 레이어 간 데이터 전송에 사용될 수는 있지만, 그 주된 목적은 도메인 모델 내에서의 값 표현과 불변성 보장이다. 레이어 간 데이터 전송에는 DTO가 더 적합한 선택이다.
마치며
가장 처음 언급했듯이, 책으로 인한 사람들의 오해로 혼용이 시작됐을 것으로 추측이 되며, 방금 막 위에서 언급한 VO가 레이어 간 데이터 전송에 사용될 수도 있기 때문에 혼용됐을 수도 있다. 이유야 어찌됐든 중요한 것은 DTO와 VO는 엄연히 다른 것임을 깨닫고 이 둘 각각의 정확한 의미를 인지하여 혼용하지 않아야 겠다는 것이다.
참고 출처
https://mrgamza.tistory.com/49
https://velog.io/@devholic/%ED%85%8C%EC%BD%94%ED%86%A1-%EA%B3%B5%EB%B6%80-DTO-vs-VO
'JAVA' 카테고리의 다른 글
업캐스팅(Upcasting) (feat. 다운캐스팅) (1) | 2024.03.05 |
---|---|
오버로딩(Overloading) vs 오버라이딩(Overriding) (2) | 2024.03.05 |
생성자를 메서드라고 할 수 있을까? (1) | 2024.02.24 |
메서드의 오버로딩(Overloading) (feat. 정적 바인딩) (0) | 2024.02.23 |
접근 제한자 (public, private, protected, default) (0) | 2024.02.21 |