학습 배경
자바와 스프링 부트로 프로그래밍을 하면서 DTO를 사용을 해왔지만, 올바른 DTO의 정의를 알지 못했고 바람직한 사용이 아니라고 생각되었습니다. DTO를 제대로 알고 어떤 상황에서 사용해야 하는지 정리하기 위해 학습했습니다.
DTO
DTO란 계층간 데이터 전송을 위해 도메인 모델 대신 사용되는 객체 입니다.
여기서 계층이란, Controller, Service, Repository 등을 의미합니다.
DTO는 순수한 데이터를 저장하고, 데이터에 대한 getter, setter 만을 가져야 한다고 정의 되어 있습니다. DTO는 어떠한 비즈니스 로직을 가져서도 안됩니다.
DTO를 왜 사용하나요?
처음에는 User 객체를 굳이 UserDto에 담에서 보내는 이유가 뭘까? 라고 생각을 했습니다.
User 그대로 보내면 굳이 리소스를 낭비하지 않고 보내서 성능이 조금이라도 더 좋아지지 않을까라는 고민을 했습니다.
하지만 알아본 결과, 애플리케이션의 안정성을 위해서 DTO가 필요할 것 같았습니다.
어플리케이션 계층간의 안정성을 확보할 수 있습니다. 위에서 말했듯이 DTO는 getter와 setter만을 가지게 됩니다. 이를 통해 실제 User객체는 DTO가 모르는 상태이고 데이터를 캡슐화 하여 안정성을 높이게 됩니다. DTO하나 만든다고 성능이 변화하지 않습니다. 굉장히 작은 부분이기 때문에.
물론 엄청나게 자주 생성되고 발생되는 객체에 DTO를 만들면 성능의 변화가 조금은 있을 수 있지만,
User객체는 엄청나게 많은 변화를 가져오는 객체가 아닐 뿐더러, 오히려 사용자의 정보를 담는 위험한 객체이기 때문에 계층간 이동을 위해 DTO가 필수적이라고 생각이 듭니다.
결론적으로는, 도메인 모델을 캡슐화 하여 보호할 수 있고, DTO를 사용하여 계층간의 결합도를 느슨하게 만들 수 있습니다.
DTO는 어떤 계층에서 까지 사용할 수 있을까
거의 대부분의 의견은 DTO가 영속성 계층, 즉 데이터 베이스에 직접 접근하여 저장되는 부분까지 도달하는 것은 지양한다.
A Service Layer defines an application's boundary [Cockburn PloP] and its set of available operations from the perspective of interfacing client layers. It encapsulates the application's business logic, controlling transactions and coor-dinating responses in the implementation of its operations
마틴 파울러는 Service 계층은 도메인을 보호하는 계층이라고 말한다. 적어도 그의 말에 따르면, 프레젠테이션 계층까지 도메인을 유출해선 안되며, 도메인은 서비스 계층에서 DTO 로 변환되어 프레젠테이션 계층으로 넘겨지는 것이 올바르다. 반대로 프레젠테이션 계층에서 비즈니스 계층으로 데이터가 전달될 떄도 DTO 형태로전달되는 것이 바람직하다.
DTO와 Entity
DTO와 Entity는 매우 밀접한 관계이다. 애초에 Entity를 이동시키는 수단으로 생긴게 DTO이기 때문이다. 그렇다면 여기서 드는 의문점은 DTO를 다른 도메인 계층으로 이동시킨 이후이다.
이런 경우 어떻게 해결해야 할까?
예를들어 id와 password를 가지는 User객체가 존재한다고 생각해보자.
public class User {
private final String id;
private final String password;
public User(final String id, final String password) {
this.id = id;
this.password = password;
}
...
...
...
}
위의 객체는 Entity이며 객체의 불변성을 위해 setter는 사용하지 않았다.
그렇다면 이를 옮기기 위한 새로운 클래스가 하나 필요하고 이를 UserDto 클래스로 만든다.
public class UserDto {
private final String id;
private final String password;
public UserDto(final String id, final String password) {
this.id = id;
this.password = password;
}
public String getId() {
return id;
}
public String getPassword() {
return password;
}
}
계층간 이동을 위한 DTO클래스를 만들었다.
이를 통해 User의 데이터를 캡슐화 하여 이동 시킬 수 있다.
하지만 우리가 해결해야 할 문제는 이 UserDto를 옮겼는데 이것을 엔티티로 어떻게 변경하지 라는 것이다.
무작정 getId getPassword로 유저 객체를 새롭게 생성하는 것은 바람직하지 못하다.
toEntity 메서드로 해결하기
정답은 없지만 가장 많이 사용되는 방법이 toEntity 메서드를 정의하는 것이다.
DTO객체 내부에서 toEntity를 구현하여 내부에서 새로운 엔티티를 생성하여 외부에서 만들어 주는 것이다. 이렇게 하면 계층간 이동후 다시 엔티티를 사용하게 되니 안정적일 것이다.
쉽게 이야기해서 A계층에서 B계층으로 이동한다고 가정할 때,
A계층에서 DTO로 변환한 뒤, B계층에 도착하고 이를 다시 Entity로 변환시킨다는 의미이다.
이것도 이해가 어렵다면 택배로 이해하면 매우 쉬워진다.
택배는 송신자가 박스에 싸서 보내고 수신자가 이를 개봉하여 사용하는 방식이다.
물론 현실세계의 택배와 조금은 다르지만 결론적으로 데이터를 안에 넣어서 보내고, 이를 바탕으로 새로운 객체를 만드는 행위가 toEntity이며 이는 기존의 데이터와 동일하기 때문에 같은 객체로 애플리케이션 내에서 인식할 수 있다.
toEntitry는 다음과 같이 구현될 수 있다.
public class UserDto {
private final String id;
private final String password;
public UserDto(final String id, final String password) {
this.id = id;
this.password = password;
}
public String getId() {
return id;
}
public String getPassword() {
return password;
}
public User toEntity() {
return new User(id, password);
}
}
위와 같이 toEntity 메서드를 구현하여 객체간 동일성이 보장된다면 같은 객체가 되기 때문에 이는 같은 엔티티라고 할 수 있고, DTO를 통해 엔티티의 데이터를 지키면서 보내는 방법이 가능한 것이다.
그럼 toEntity를 반대로 하면 toDto로도 될 수 있지 않을까 라는 생각이 바로 들었다.
public class User {
private final String id;
private final String password;
public User(final String id, final String password) {
this.id = id;
this.password = password;
}
public UserDto toDto() {
return new UserDto(id, password);
}
}
이런식으로 생성이 가능하다고 생각이 들었다. 맞는지 확실하지는 않다.
이번에 DTO를 학습하면서 왜 사용하는지 어떻게 사용해야 되는지 이해할 수 있었다.
'BackEnd > Java' 카테고리의 다른 글
우테코 프리코스 1주차 미션 회고 및 정리 - 1 (0) | 2024.10.21 |
---|---|
[Java] 스트림이란? 스트림의 사용이유와 컬렉션과 비교 예제 (0) | 2024.10.20 |
[Test] TDD - Test Driven Development (0) | 2024.10.04 |
[Java] synchronized - 동기화 (0) | 2024.08.19 |
[Java] 메모리 가시성 - volatile (0) | 2024.08.19 |