Introduction
새롭게 공부를 시작하면서 Entity, DTO, VO의 대한 명확한 역할이 궁금해졌다. 새로운 개념들을 정확하게 이해하고 설명하기 위해 이번 포스팅에서는 이것들이 무엇이고, 언제 사용하며, 또 차이점은 무엇인지 정리하려고 한다.
먼저 Entity와 DTO, VO를 복잡하게 나누는 이유가 무엇일지 고민해보자.
그 이유는 소프트웨어를 설계하면서 명확한 역할을 분담하고, 유지보수성과 확장성을 높이기 위해서이다. 이를 구분함으로써 복잡한 시스템을 안정적으로 유지할 수 있다.
1. DTO란?
DTO(Data Transfer Object)는 데이터 전송 객체라는 의미를 가지고 있다.
계층간 데이터 교환을 위한 객체(java beans)이며, 데이터를 담아서 전달하는 바구니라고 생각하면 된다.
- DB에서 데이터를 얻어 Service나 Controller 등 어플리케이션 간의 데이터를 전송하기 위해 사용
- 주로 비동기를 처리할 때 사용
- 로직을 가지고 있지 않으며, getter / setter 메소드만 사용
Setter 사용의 문제점
Client -> Controller -> Service -> Repository -> DB 방향으로 데이터가 이동할 때에는 View에서 받은 값을 DTO 클래스에 할당하기 때문에 Setter가 사용되는것이 문제가 없다.
하지만, 반대의 경우를 보면 Repository(Persistence Layer)에서 Service(Business Layer)로 전달할 때 Setter가 사용된다면 DB의 데이터가 변경되어 버그를 유발하거나 데이터 무결성이 손상될 수 있다.
그렇다면 어떻게 해야할까?
Setter 대신 불변성을 유지할 수 있는 생성자나 Builder를 사용하면 된다.
생성자나 Builder를 통해서만 값을 초기화할 수 있기 때문에, 전달 과정 중에 데이터가 의도하지 않는 방식으로 변경되는 것을 방지한다.
[ 생성자 사용 방식 ]
public class SampleDto {
private String name;
private int price;
// 생성자
public SampleDto(String name, int price) {
this.name = name;
this.price = price;
}
// getter
public String getName() {
return name;
}
public String getPrice() {
return price;
}
}
[ Builder 사용 방식 ]
OrderDto orderDto = OrderDto.builder()
.uuid(uuid)
.memo("문앞 보관 해주세요.")
.orderDateTime(LocalDateTime.now())
.orderStatus(OrderStatus.OPENED)
.memberDto(
MemberDto.builder()
.name("lee")
.nickName("yeoooo")
.address("숲 속")
.build()
)
2. Entity란?
Entity(엔티티)는 DB 테이블과 실제로 매핑(mapping)되는 객체이다.
데이터베이스의 테이블의 모든 컬럼을 필드로 사용한다.
- 요청(request)나 응답(response) 데이터를 전달하는 객체로 사용 금지
- 객체의 불변성을 보장하기 위해 setter 메소드를 지양하고 생성자나 Builder를 사용
- 상속받거나 구현체이면 안됨
데이터 교환하는 용도로 사용하면 안되는 이유?
Entity를 기준으로 데이터베이스 테이블이 생성한다. 많은 서비스 클래스나 비즈니스 로직이 Entity 클래스에 의존하여 동작하기 때문에 Entity 클래스가 변경되면 여러 클래스에 영향을 주게 된다.
그렇기에 요청이나 응답 값을 전달하는 클래스에는 DTO를 사용하는 것이 더 좋다.
3. VO란?
VO(Vlaue Object)로 값 그 자체를 표현하는 객체이다.
객체의 정보가 변경되지 않은 '불변성'을 보장한다. 주로 특정 값이나 개념을 묘사할 때 primitive type 대신 사용한다.
(ex) 돈이라는 값을 표현할 때 int money 보다 Money money를 사용하면 명확하게 표현이 가능하다
VO를 별도로 생성하여 특정 값을 표현하면 이와 관련된 작업들을 모두 VO에서 처리할 수 있다.
- VO 내부에서 선언된 속성의 모든 값들이 모두 같아야 동일한 객체라고 판별
- 서로 다른 이름을 갖는 VO 인스턴스라도 모든 속성 값이 같으면 두 인스턴스는 같은 객체로 인식
- VO에서는 객체를 속성 값만 비교할 수 있도록 object 클래스의 equals()와 hashcode()를 오버라이딩하여 구현
- getter / setter 외에도 비즈니스 로직을 사용 가능
Java Compiler가 해시 코드가 아닌 필드값으로 인스턴스 간 동등성 비교를 위해 위 메소드를 필수로 구현해야 한다.
대표적인 예시로는 돈이 있는데, 여러 오만원짜리 지폐의 고유번호가 달라도 같은 오만원으로 보는 것처럼 "값"자체로만 비교한다.
public class Money{
private final int value;
public Money(int value) { // 생성자
this.value = value;
}
public int getHalfValue(){ // setter/getter 외의 로직 추가 가능
return value/2;
}
// equals()와 hashcode()를 오버라이딩
@Override
public boolean equals(Object o) {
if(this == o) return true;
if(!(o instanceof Money)) retrun false;
Money money = (Money) o;
return value == money.value;
}
@Override
public boolean hashCode() {
return Objects.hash(value);
}
}
Entity , DTO , VO 정리
분류 | Entity | DTO | VO |
정의 | 실제 DB 테이블과 매핑되는 객체 | 데이터 전달용 객체 | 값 자체를 표현하는 객체 |
상태 변경 여부 | 불변, 가변 객체 | getter만 사용 : 불변 객체 getter, setter 사용 : 가변 객체 |
불변 객체 |
로직 포함 여부 | 도메인 로직 포함 가능 | getter, setter만 사용 가능 | 비즈니스 로직 포함 가능 |
Reference
https://dev-coco.tistory.com/87#head3
DTO와 VO 그리고 Entity 차이점 알아보기
Entity 란? Entity 클래스는 실제 DataBase의 테이블과 1 : 1로 매핑되는 클래스로, DB의 테이블내에 존재하는 컬럼만을 속성(필드)으로 가져야 합니다. Entity 클래스는 상속을 받거나 구현체여서는 안되
dev-coco.tistory.com
https://jihoon2723.tistory.com/24
VO, DAO,DTO, Entity, Domain 클래스 비교하기!!
jsp/servlet에서 mvc패턴을 공부할 때 DAO DTO VO 를 접했었다. 그런데 그당시엔 그냥 받아들이기만 했었던것 같다. 최근 SpringBoot공부를 하면서 웹페이지를 만들어보려 하는데 mvc패턴을 쓰고자 여기저
jihoon2723.tistory.com