프로젝트를 진행하던 중 DTO를 개념만 알고 어디서 사용해야 적절한지에 대해서 고민해보지 않고 사용해서 정리를 해보려고 한다. 그래서 어디서 사용해야 적절한 것인지 알아보려 한다.
➡️DTO
DTO(Data Transfer Object)란 계층 간 데이터 교환을 위해 사용하는 객체이다.
MVC패턴으로 예를 들어보자
MVC 패턴은 애플리케이션을 개발할 때 그 구성요소를 Model과 View 및 Controller 등 세 가지 역할로 구분하는 디자인 패턴이다. 비즈니스 처리 로직(Model)과 UI영역(View)은 서로의 존재를 모르고 Controller가 중간에서 Model과 View를 연결하는 역할을 한다.
Controller는 View로부터 들어온 사용자 요청을 해석하여 Model을 업데이트하거나 Model로부터 데이터를 받아 View로 전달하는 작업을 수행한다.
MVC 패턴의 장점은 Model과 View를 분리함으로써 서로의 의존성을 낮추고 독립적인 개발을 가능하게 한다.
Controller는 View와 도메인 Model의 데이터를 주고받을 때 별도의 DTO 를 주로 사용한다.
왜냐하면 도메인 객체를 View에 직접 전달할 수 있지만, 민감한 도메인 비즈니스 기능이 노출될 수 있으며 Model과 View 사이에 의존성이 생기고 비즈니스 로직 등 민감한 정보가 외부에 노출되기 때문에 보안상의 문제가 생길 수 있기 때문이다.
문제가 생길수 있는 예제를 한번 보자
public class User{
public Long id;
public String name;
public String email;
public String password;
}
@GetMapping
public ResponseEntity<User> findeUser(@PathVariable long id) {
User user = userService.findById(id);
return ResponseEntity.ok(user);
}
- 이런 식으로 컨트롤러가 클라이언트의 요청에 대한 응답으로 도메인 모델인 User를 넘겨주면 다음과 같은 문제점이 발생한다.
- 도메인 모델의 모든 속성이 외부에 노출된다. 저 요청에서 사용하지 않는 데이터를 불필요하게 가지고 있는 것이다.
- 모든 속성이 외부에 노출되기 때문에 보안상 좋지 않다.
- View에서 Model의 메서드를 호출하거나 상태를 변경시킬 위험이 존재한다.
- Model과 View가 상호 의존성이 높아져서 View에 요구사항이 있을 경우 Model에 영향을 끼칠 수 있다.
이제 DTO를 사용해보자
public class UserDto {
public long id;
public String name;
public String email;
@Builder
public UserDto(long id,String name,String email){
this.id = id;
this.name = name;
this.email = email;
}
}
@GetMapping
public ResponseEntity<UserDto> findUser(@PathVariable long id) {
UserDto userDto = userService.findById(id);
return ResponseEntity.ok(userDto);
}
이런 식으로 DTO를 사용하면 도메인 모델을 캡슐화하고, View에서 사용하는 데이터만 선택적으로 응답할 수 있다. DTO는 클라이언트 요청에 포함된 데이터를 담아 서버 측에 전달하고, 서버 측의 응답 데이터를 담아 클라이언트에 전달하는 계층 간 전달자 역할을 한다.
이제 DTO의 사용범위에 대해서 알아보자
위 그림은 Layered Architecture이다. 유사한 관심사를 레이어로 나눠서 추상화하여 수직적으로 배열하는 아키택처이다. 하나의 계층은 주어진 역할을 수행하고, 인접한 다른 계층과 상호작용한다. 이런 식으로 시스템을 계층으로 나누면 시스템 전체를 수정하지 않고도 특정 계층을 수정 및 개선할 수 있어 재사용성과 유지보수에 유리하다.
한 로직을 예로 들어보자
View로부터 받아온 DTO를 Controller에서 도메인(Entity)으로 변환하고 서비스 계층에게 이를 전달하여 작업을 수행하고 서비스 계층은 Controller에게 도메인을 반환하고, Controller는 도메인을 DTO로 변환해 View로 응답을 보낸다.
여기서 보면 Controller가 도메인을 DTO로 변환을 한다. 그러나 잘 생각해보면 굳이 Controller에서 변환을 해야 하나 라는 생각이 든다.
서비스 계층이 요청으로 DTO를 받고 응답으로 DTO를 보내줘도 아무 문제가 없다. 그러면 어느 계층에서 사용하는 것이 자연스러운지 알아보자
🪐Repository
Repository 계층은 Entity의 영속성을 관장하는 역할이라고 명시되어있다. 이로 인해, 표현 계층에서 사용할 도메인 계층을 DTO로 변환하는 작업을 책임지게 하는 것을 지양하자는 다수의 의견이 존재한다.
대부분은 Controller와 Service 계층에 위치시켰다.
🪐Service
마틴 파울러의 말을 인용해보자면 Service계층이란 애플리케이션의 경계를 정의하고 비즈니스 로직 등 도메인을 캡슐화하는 역할이라고 정의한다. 즉, 도메인 모델을 표현 계층에서 사용하는 경우 결합도가 증가하여 도메인의 변경이 Controller의 변경을 촉발하는 유지보수의 문제로 이어질 수 있다.
이러한 관점에서 바라볼 때, 레이어 간 데이터 전달 목적으로 DTO를 엄격하게 고수한다면 변환 로직이 Service 레이어에서 정의되어야 한다는 의견이 존재했습니다. 요청에 대한 응답 역시 Service 레이어의 일부분이기 때문이다.
Service가 DTO를 사용하는 경우 Controller가 View로부터 받은 DTO를 Entity로 변환한 뒤, Service 레이어가 Entity를 전달받아 일련의 비즈니스 로직을 수행한다고 가정해보자.
- 복잡한 애플리케이션의 경우 Controller가 View에서 전달받은 DTO만으로 Entity를 구성하기란 어렵다. Repository를 통해 여러 부수적인 정보들을 조회하여 Domain 객체를 구성할 수 있는 경우도 존재하기 때문이다.
- Controller에서 DTO를 완벽하게 Domain 객체로 구성한 뒤 Service에 넘겨주려면, 복잡한 경우 Controller가 여러 Service(혹은 Repository)에 의존하게 된다.
블로그와 깃 헙을 보면서 정답을 찾으려 했지만 대부분의 사람들이 명확한 답이 없고 상황에 따라 맞게 써야 한다고 한다.
Should services always return DTOs, or can they also return domain models?
I'm (re)designing large-scale application, we use multi-layer architecture based on DDD. We have MVC with data layer (implementation of repositories), domain layer (definition of domain model and
stackoverflow.com
여기서 많은 개발자들이 서비스가 무조건 DTO를 반환해야 하는지 아니면 도메인 모델을 반환해도 되는지에 대한 질문이다.
여기서 나온 답을 정리해보자면
- 서비스 계층은 애플리케이션의 경계를 정의하고 도메인을 캡슐화한다.
- DTO는 말 그대로 데이터 전달용 객체이고, communication에 사용된다면 의미가 있다. 대신 Presentation 레이어에서 도메인 모델을 사용하게 되면, 결합도가 증가해 코드 변경이 불가피할 수 있다
- 도메인 모델과 똑같은 DTO를 만드는 것 같고 의미 없게 느껴진다 : DTO의 단점 중 하나이다. 대신 지금은 코드 중복으로만 생각되지만, 나중에 프로젝트가 확장되면 더 큰 의미를 발휘할 수 있다.
대규모 프로젝트에는 DTO를 사용하면 더 큰 의미를 발휘할 수 있다고 한다.
어느 블로그에서 본 걸로 인용하지만 DTO사용범위라는 고민보다는 도메인 모델 보호라는 관점에서 생각해보자고 한다. 나도 이 의견에 동의한다.그리고 Repository단위는 다들 공통적으로 지양한다.
📁정리
DTO를 공부하면서 많은 고민을 해봤다. 내가 아직 많이 부족해서 답을 결정짓기 어려운 내용인 거 같다. DTO는 상황에 맞게 사용하고 무한한 피드백으로 최적의 케이스를 찾아야 한다. 정리하자면 DTO를 남발하는 것은 좋지 않다. 상황에 따라 DTO가 필요 없을 수도 있다.
이렇게 의견이 다 다르고 정답이 없는 건 처음 느껴본다. 공부를 무한히 하고 많은 사람들과 토론을 해보는 것도 성장에 많은 도움이 될 것 같다.
'Dev > SpringData' 카테고리의 다른 글
[Querydsl] Projection 정리 (0) | 2021.12.12 |
---|---|
[Querydsl] QueryDSL 사용법 정리 (0) | 2021.12.03 |
[JPA]Fetch.Lazy를 설정했을때 @OneToOne에서 발생하는 이슈 (0) | 2021.10.13 |
[JPA] 엔티티 매핑 (0) | 2021.09.10 |
Querydsl 오류 (querydsl error cannot find symbol) (0) | 2021.09.02 |