3대450(이었던) 킹갓인비의 2월 25일 DTO vs VO 테코톡을 정리해보자!
🏈 결론
- DTO : 데이터 전달용 객체
- VO : 값 표현용 객체
????
그렇다. 결론부터 이야기 해야 이해가 쉽다.
결론을 알았으니 이제부터 DTO와 VO에 대해 천천히 알아보자.
🏈 DTO 란?
Data Transfer Object
데이터를 전달하기 위해 사용하는 객체이자, 데이터를 담아서 전달하는 바구니. 계층간 데이터를 전달하기 위한 객체다.
주로 Entity class(DB 테이블과 직접적으로 매칭되는 클래스)와 함께 사용된다.
그대로 전달해도 될 데이터를 굳이 DTO, Entity class로 나누고, 묶는 이유는 무엇일까? 바로 View 때문이다.
View는 비즈니스 요구사항에서 가장 자주 변경되는 계층이다. View가 변경될 때마다 DB 테이블에 이를 반영하는 건 엄청난 오버헤드를 발생 시킨다. 그렇다고 DB 테이블과 직접적으로 매칭되는 Entity class에 이를 반영하기엔, Entity class 와 의존관계를 맺은 수 많은 클래스들에도 영향을 끼치게 된다. 즉, 매번 View의 변경사항을 DB나 Entity class에 반영하는 건 사실상 불가능하다.
이러한 문제점을 해결하기 위해 다른 클래스들로부터 완전히 독립적으로, 자유롭게 변환이 가능한 DTO 객체 개념이 탄생하게 된 것이다.
DTO 객체는 단순하다. 순수하게 데이터 전달만을 위해 사용되기 때문에 오직 getter/setter만 사용한다.
(순수하게 getter/setter만 가진 객체가 DTO라고 말하는 개발자들도 많지만, 간단한 형태변환 정도는 허용하는 개발자들도 존재한다.
DTO도 결국 Object이므로 말이 된다. 적당한 타협이 필요하다.)
아래 예시코드로 구조를 살펴보자.
// DTO
public class CrewDto {
private String name;
private String nickname;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getNickname() {
return nickname;
}
public void setNickname(final String nickname) {
this.nickname = nickname;
}
}
정말 기본적인 getter/setter 만 가지고 있으며, 별도의 로직을 포함하지 않는다.
// Service Layer - SampleService
public CrewDto createNewCrew() {
String newName = "김태희";
String newNickname = "Inbi";
CrewDto crewDto = new CrewDto();
crewDto.setName(newName);
crewDto.setNickname(newNickname);
return crewDto;
}
// Web(Controller) Layer
public String createNewCrew() {
CrewDto newCrewDto = sampleService.createNewCrew();
String nameOfNewCrew = newCrewDto.getName();
String nicknameOfNewCrew = newCrewDto.getNickname();
return nameOfNewCrew + nicknameOfNewCrew;
}
Service 계층에서 Web(Controller) 계층으로 크루 정보를 전달 할 때 DTO를 통해서 받는다. 반대의 경우에도 마찬가지다.
🏈 DTO도 불변 객체로
객체지향 프로그래밍에 대해 공부하다보면 “가변 객체를 지양하라”는 말을 자주 볼 수 있다. 이 이야기는 DTO에도 예외 없이 적용된다. DTO 또한 setter 메서드를 제외시키고 생성자를 통해 값을 지정함으로서 불변 객체로 만들어 줄 수 있다.
// DTO
public class CrewDto {
private final String name;
private final String nickname;
public CrewDto(final String name, final String nickname) {
this.name = name;
this.nickname = nickname;
}
public String getName() {
return name;
}
public String getNickname() {
return nickname;
}
}
setter를 사용할 때보다 코드도 간결해졌다. 역시 불변 객체가 짱이다.
🏈 VO 란?
Value Object
말 그대로 값을 표현하는 객체다. (돈, 이름 등…) 현실세계에도 VO 개념이 존재한다.
DK0577701L
, AB0688463C
, BH0640025K
, BJ7783675C
지폐의 일련번호는 모두 다르지만, 우리는 이것들을 모두
“50,000원권” 이라고 부른다. 이 “50,000원권” 개념이 바로 VO다.
아래 예시 코드로 구조를 살펴보자.
public class Money {
private final int value;
public Money(final int value) {
this.value = value;
}
public int getHalfValue() {
return value / 2;
}
...
}
VO는 값 그 자체를 표현하는 객체이므로 무조건 불변이어야한다. 그래서 setter가 없어야 한다. 그렇지만 VO는 getter 이외의 별도 로직을 가질 수 있다!
앞서 VO는 “50,000원권” 개념이라고 했다. 그런데 자바 컴파일러는 기본적으로 인스턴스간 비교를 해시코드를 통해서 진행한다.
즉, 현재 Money
인스턴스끼리 동등한가를 물으면 컴파일러는 “다르다!” 라고 대답할 것이다.
이 때문에 VO는 자바 컴파일러가 인스턴스간 동등성 비교를 해시코드가 아닌 필드 값으로 하도록 유도하기 위해서
equals
, hashCode
메서드를 필수로 구현(오버라이딩)해야한다!
equals
, hashCode
메서드 오버라이딩을 통해서 VO 끼리는 서로 다른 인스턴스더라도, 가지고 있는 값(value)가 같다면
같은 인스턴스로 취급하게 된다.
public class Money {
private final int value;
public Money(final int value) {
this.value = value;
}
public int getHalfValue() {
return value / 2;
}
@Override
public boolean equals(Object o) {
if(this == o) {
return true;
}
if (!(o instanceof Money)) {
return false;
}
Money money = (Money) o;
return value == money.value;
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
🏈 DTO vs VO
DTO와 VO에 정리가 끝났으니, 포스팅의 주제였던 DTO vs VO로 되돌아와보자.
DTO vs VO 주제는 많은 개발자들이 두 개념을 혼동하다보니 자주 떠오른다. 분명한 차이를 가지고 있음에도 많인 개발자들이 혼동하는 이유는 무엇일까?
… 이 책이 원인일 가능성이 농후하다고 한다.
우리는 이미 DTO와 VO의 차이를 명확하게 이해했다. 마지막 표로 다시 한 번 정리해보자.
DTO | VO | |
---|---|---|
용도 | 계층간 데이터 전달 | 값 표현 |
동등성 결정 | 값이 같다고 같은 객체는 아님 | 값이 같으면 같은 객체 |
가변 객체 | (권장하지 않지만) setter를 포함시키면 가변 객체 가능 | 절대 안됨. 무조건 불변 |
로직 | (취향차이지만) getter/setter 외 로직이 없다 | getter/setter 외 로직을 가질 수 있다 |
끗!
References
- 2021-02-25 인비 테코톡
- [DAO] DAO, DTO, Entity Class의 차이
댓글남기기