DTO(Data Transfer Object)란 계층간 데이터 교환을 위해 사용하는 객체(Java Beans)다.
참고 - 🔗DTO란?
Controller는 View와 도메인 Model의 데이터를 주고 받을 때 별도의 DTO를 주로 사용한다.
도메인 객체를 View에 직접 전달할 수 있지만, 민감한 도메인 비즈니스 기능이 노출될 수 있으며 Model과 View 사이에 의존성이 생기기 때문!
(소규모 프로젝트는 DTO 사용이 불필요한 경우도 있다)
User.java
public class User {
public Long id;
public String name;
public String email;
public String password; //외부에 노출되서는 안 될 정보
public DetailInformation detailInformation; //외부에 노출되서는 안 될 정보
//비즈니스 로직, getter, setter 등 생략
}
UserController.java
@GetMapping
public ResponseEntity<User> showArticle(@PathVariable long id) {
User user = userService.findById(id);
return ResponseEntity.ok().body(user);
}
도메인 Model로 User를 넘겨줄 때 발생할 수 있는 문제점
UserDto.java
public class UserDto {
public final long id;
public final String name;
public final String email;
//생성자 생략
public static UserDto from(User user) {
return new UserDto(user.getId(), user.getName(), user.getEmail());
}
}
UserController.java
@GetMapping
public ResponseEntity<UserDto> showArticle(@PathVariable long id) {
User user = userService.findById(id);
return ResponseEntity.ok().body(UserDto.from(user));
}
반면 DTO를 사용하면 앞서 언급된 문제들을 쉽게 해결할 수 있습니다. 도메인 Model을 캡슐화하고, UI 화면에서 사용하는 데이터만 선택적으로 보낼 수 있습니다.
ArticleController.java
@PostMapping
public ResponseEntity<ArticleResponseDto> createArticle(@RequestBody ArticleRequestDto articleRequestDto) {
//로직 생략
Article article = articleRequestDto.toEntity();
Article savedArticle = articleService.createArticle(article);
ArticleResponseDto articleResponseDto = ArticleResponseDto.from(savedArticle);
return ResponseEntity.ok().body(articleResponseDto);
}
ArticleService.java
public Article createArticle(Article article) {
//로직 생략
return articleRepository.save(article);
}
이때 드는 궁금증 : “꼭 DTO와 Domain간의 변환 위치가 Controller(표현 계층)여야 하는가?”
ArticleService.java
public ArticleDto createArticle(ArticleDto articleRequestDto) {
Article article = articleRequestDto.toEntity();
//로직 생략
return ArticleDto.from(articleRepository.save(article));
}
이렇게 Service 레이어가 요청으로 DTO를 받고 응답으로 DTO를 보내줘도 동작에 문제가 없기 때문입니다 .
Repository 레이어는 Entity의 영속성을 관장하는 역할이라고 명시되어 있습니다. 이로 인해, 표현 계층에서 사용할 도메인 계층의 Aggregates를 DTO로 변환하는 작업을 Repository 단에서 책임지게 하는 것을 지양하자는 의견이 다수 존재했습니다.
실제로 이 글을 작성하면서 DTO와 Entity간의 변환과 관련된 여러 문서들을 참조했는데, 모두가 변환 로직을 Controller 혹은 Service 레이어에 위치시켰습니다. 그렇다면 DTO의 사용 범위 및 Entity간의 변환 위치는 Controller와 Service 중 어느 곳이 적합할까요? 🧐
DTO-Entity 간의 변환 위치는 Service 레이어가 타당에 보인다.
하지만 Service레이어에 DTO가 드렁오지 않아야 여러 종류의 컨트롤러에서 해당 서비스를 사용할 수 있다.
하지만 현업을 하다보면 여러 종류의컨트롤러가 한 서비스를 사용하기 보단, 한 종류의 컨트롤러가 서비스를 쓰기 때문에 너무 엄격하게 제안할 필요도 없다
- 서비스로 DTO 진입을 허용하되, 서비스 메소드 상위에서 DTO 체크 및 도메인 변환을 하고 변환 후에는 DTO는 사용하지 않고 도메인만 사용하도록 구현한다
- 상황에 맞게 사용하자