스프링 MVC 기반 프로젝트를 하다 보면 DTO, VO, Entity 객체를 만들어서 사용한다. 각 클래스의 멤버변수들이 비슷한 값으로 지정되기 때문에 혼용하여 많이 사용하게 된다. 특히 DTO와 VO는 동일한 개념으로 사용되는 경우도 많아 이참에 각각의 클래스마다 정의와 특징을 살펴보자.
DTO(Data Transfer Object)?
DTO는 데이터를 전달하기 위한 객체라고 정의된다. 말 그대로 계층 간의 데이터를 주고받을 때 사용되는 객체라고 생각하면 된다. MVC 관점에서 본다면 View와 Controller 사이, Controller와 Service 사이 등 계층 간 데이터를 주고받을 때 사용된다.
DTO의 특징으로는 getter/setter 메소드를 포함하며, 이 외의 비즈니스 로직은 포함하지 않는다. 왜냐하면 DTO는 순수하게 데이터 전송을 위한 용도이기 때문이다.
getter/setter 메서드를 활용하여 구현하면 아래 코드와 같다.
public class MemberDto {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
위 구조는 DTO의 기본 구조이지만 setter를 활용하여 구현하므로 데이터가 가변 한다는 문제가 있다. 불변성을 보장해주지 않는 것은 위험을 노출할 수 있다. 그렇기 때문에 setter 대신 생성자를 이용해 불변객체 형태로 만들 수 있다.
public class MemberDto {
private final String name;
private final int age;
public MemberDto(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
여기서 한 단계 더 접근한다면 생성자는 아무래도 객체 생성시 코드가 간결하게 가져갈 순 있지만 어떤 자리에 어떤 값을 넣어줘야 할지 인지하고 있어야 해 유연성과 가독성이 떨어지게 된다. 이러한 문제를 해결하기 위해 DTO에 Builder 패턴을 활용하여 사용하기도 한다.
@Builder
@Getter
public class MemberDto {
private final String name;
private final int age;
}
Builder 패턴을 사용하면 코드를 유연하고 가독성있게 구성할 수 있다.
Entity?
Entity 클래스는 실제 데이터베이스의 테이블과 1:1로 매핑되는 클래스이며, 데이터베이스 테이블 내에 존재하는 칼럼만을 필드로 가져야 한다.
Entity 클래스는 DB 테이블과 매핑되기 때문에 id값(식별자)으로 구분될 수 있으며 DTO와 마찬가지로 불편객체형태로 구성한다. 하지만 변경값 필요한 경우에 대해서는 로직을 구성하여 적용한다. (단순 setter를 사용하는 게 아니라 변경의 책임을 Entity 객체가 갖는 형태로 로직을 구성한다.)
public class Member {
private final Long id;
private final String email;
private final String password;
private final Integer age;
public Member() {
}
public Member(Long id, String email, String password, Integer age) {
this.id = id;
this.email = email;
this.password = password;
this.age = age;
}
}
Entity와 DTO를 분리하는 이유
코드구성만 본다면 Entity나 DTO의 차이는 커 보이지 않는다. 그런데 왜 분리하여 사용하는 것일까? Entity와 DTO를 분리하여 관리하는 이유는 DB계층과 View계층 사이 역할을 분리하기 위함이다.
설명하자면, Entity는 실제 DB 테이블이 매핑되는 클래스이며, 변경이 일어나면 다른 클래스에 영향을 미칠 수 있다. View 단에서 넘어오는 데이터는 일관적이지 않은 구조이다. 그렇기 때문에 Entity를 DTO의 역할처럼 요청과 응답 값으로 사용한다면 VIew가 변경될 때마다 Entity 클래스도 그에 맞춰서 변경이 돼야 한다.
프로젝트의 수 많은 서비스클래스와 그 안에 비즈니스 로직들은 Entity를 기준하여 동작된다. 그러므로 변경이 이뤄지면 그만큼 위험요소가 발생하게 된다. 그러므로 View 변경에 대응할 수 있고 다른 클래스에도 영향을 주지 않은 DTO를 사용한다.
결론적으로 Entity는 요청이나 응답값을 전달하는 클래스로 이용하면 안 되고, DTO를 이용하여 분리해서 사용해야 한다.
VO(Value Object)
VO는 말 그대로 값 객체라는 의미를 가지고 있다. DTO와 다르게 VO는 Read-Only 속성을 가진다.
자바에서 단순히 값 타입을 표현하기 위해 불변 클래스(Read-Only)를 만들어 사용한다.
VO의 핵심 역할은 equals()와 hashcode()를 오버라이딩하는 것이다. 오버라이딩 함으로써 내부에 선언된 속성의 모든 값들이 VO 객체마다 값이 같다면, 똑같은 객체로 판별하게 된다.
VO는 getter 메소드와 함께 비즈니스 로직도 포함될 수 있다. 단, 값을 변경할 수 있는 setter 메서드는 사용하면 안 된다.
public class Money {
private final String currency;
private final int value;
public Money(String currency, int value) {
this.currency = currency;
this.value = value;
}
public String getCurrency() {
return currency;
}
public int getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return value == money.value && Objects.equals(currency, money.currency);
}
@Override
public int hashCode() {
return Objects.hash(currency, value);
}
}
// MoneyTest.java
public class MoneyTest {
@DisplayName("VO 동등비교를 한다.")
@Test
void isSameObjects() {
Money money1 = new Money("원", 10000);
Money money2 = new Money("원", 10000);
assertThat(money1).isEqualTo(money2);
assertThat(money1).hasSameHashCodeAs(money2);
}
}
DTO와 VO의 차이
DTO와 VO 는 Data를 전달하는 객체로 동일한 개념이지만, DTO는 어느 정도 가변의 성격을 지니고 있지만 VO는 값 그 자체의 의미를 가진 불편 클래스(Read-Only)를 의미한다. 그리고 VO는 DTO에 없는 비즈니스 로직을 가질 수 있다.
이러한 특징으로 인해, DTO는 단순한 통신 용도로 사용되며 VO는 특정 데이터의 검증이나 전달할 때 사용된다.
Reference
https://tecoble.techcourse.co.kr/post/2021-05-16-dto-vs-vo-vs-entity/
'Framework > Spring' 카테고리의 다른 글
[스진초5기/Spring] DTO, Entity 변환은 어느 계층에서 일어나야할까? (0) | 2023.11.16 |
---|---|
[스진초5기/Spring] 스프링 3계층과 DI의 관계 (0) | 2023.11.05 |
[스진초5기/Spring] 그래서 Controller가 뭐야? (1) | 2023.11.03 |
[스진초5기/Spring] Controller, Service, Repository (3계층) (0) | 2023.10.31 |
[스진초5기/Spring] IoC, DI 정리 (0) | 2023.10.30 |