DTO와 VO의 혼용 사례와 원인
구글링을 해 보면 사람들이 DTO와 VO를 혼용하여 쓰는 경우가 많습니다.
원인
core J2EE Patterns 책의 1판에서는 데이터 전달용 객체를 VO로 정의했지만, 2판부터는 혼동의 여지 때문에 TO로 재정의하였기 때문으로 추측됩니다. 그리고 현재 데이터 전달용 객체의 정의는 D를 붙여 DTO로 정의하고 있습니다.
혼동의 여지가 있어 수정한 정의가 오히려 혼동을 초래했습니다. 이름이 다른 만큼 구별해 보겠습니다.
결론 먼저!!
DTO는 데이터 전달용이며, VO는 값 표현용입니다.
DTO란?
정의
- Data Transfer Object
- 데이터를 전달하기 위해 사용하는 객체 (계층 간 데이터를 전달)입니다.
- 데이터를 담아서 전달하는 바구니입니다.
특성
- 오직 getter/setter 메서드만을 갖습니다.
- setter 메서드를 가질 경우 데이터는 가변적으로 변경됩니다.
- 다른 로직을 갖지 않습니다. 순수하게 데이터 전달만을 위한 객체이기 때문입니다.
예시
public class CrewDto {
private String name;
private String nickname;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}
// Service layer
public CrewDto createNewCrew() {
String newName = "김태희";
String newNickname = "Inbi";
CrewDto crewDto = new CrewDto();
crewDto.setName(newName);
crewDto.setNickname(newNickname);
return crewDto;
}
// Web layer
public String createNewCrew() {
CrewDto newCrewDto = sampleService.createNewCrew();
String nameOfNewCrew = newCrewDto.getName();
String nicknameOfNewCrew = newCrewDto.getNickname();
return nameOfNewCrew + nicknameOfNewCrew;
}
DTO를 불변객체로!
DTO에서 setter 메서드들을 없애면 전달 과정 중에서의 데이터 불변성을 보장할 수 있어 더욱 안정적입니다.
DTO Class와 Entity Class
Entity 클래스는 절대로 요청이나 응답 값을 전달하는 클래스로 사용하면 안 됩니다. Entity 클래스는 데이터베이스와 매핑되어 있는 핵심 클래스이기 때문입니다.
Entity 클래스를 기준으로 테이블이 생성되고 스키마가 변경되며, 뷰는 비즈니스 요구사항에서 자주 변경되는 부분입니다.
만약 Entity 클래스를 요청이나 응답 값을 전달하는 클래스로 사용한다면, 뷰가 변경될 때마다 Entity 클래스를 그에 맞춰 매번 변경해야 합니다.
수많은 클래스나 비즈니스 로직들이 Entity 클래스를 기준으로 동작하기에, Entity 클래스를 변경하면 이러한 얽힌 많은 클래스들에 영향을 끼치게 됩니다.
따라서 요청이나 응답 값을 전달하는 클래스로는 반드시 뷰의 변경에 따라 다른 클래스들에게 영향을 끼치지 않고 자유롭게 변경할 수 있는 DTO들을 사용해야 합니다.
또한 응답 값으로 여러 테이블들을 조인한 결괏값을 줘야 할 경우가 빈번하기 때문에, Entity 클래스만으로는 응답 값을 표현하기 어려운 경우가 많습니다.
Entity와 DTO를 분리합시다!
VO란?
정의
- Value Object
- 값 그 자체를 표현하는 객체입니다.
예시
예시로는 돈을 들 수 있습니다. 만 원짜리 지폐가 고유번호가 서로 다르다고 해서 다른 만 원이라고 하지 않고, 똑같은 만 원이라고 판단합니다.
public class Money {
private final int value;
public Money(final int value) {
this.value = value;
}
public int getHalfValue() {
return value / 2;
}
// 일반적인 객체는 기본적으로 equals, hashcode 비교가 모두 불일치로 나오기 때문에 재정의해야 합니다.
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Money)) {
return false;
}
Money money = (Money) other;
return value == money.value;
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
HashXXX (HashSet, HashMap, HashTable)의 동등 비교 방식
이펙티브 자바에서도 나오는 내용입니다. 요약하자면, 두 객체를 커스텀하게 같을 조건을 설정하기 위해서는 Object 클래스의 equals와 hashcode를 재정의해야 한다는 것입니다. 이펙티브 자바를 정리할 때 새로 달아두겠습니다.
DTO vs VO
DTO | VO | |
용도 | 레이어 간 데이터 전달 | 값 자체 표현 |
동등 결정 | 속성값이 모두 같다고 해서 같은 객체가 아닙니다. | 속성값이 모두 같으면 같은 객체입니다. |
가변 / 불변 | setter 존재 시 가변, 비존재 시 불변입니다. 가급적 불변을 권장합니다. | 불변 |
로직 | getter/setter 외의 로직을 갖지 않습니다. | getter/setter 외의 로직을 가질 수 있습니다. |