DTO를 어디까지 사용하는게 좋을까?

·2023년 7월 28일
1

프로젝트 공부

목록 보기
23/33

DTO?

DTO(Data Transfer Object)란 계층간 데이터 교환을 위해 사용하는 객체(Java Beans)다.
참고 - 🔗DTO란?

MVC 패턴에서

Controller는 View와 도메인 Model의 데이터를 주고 받을 때 별도의 DTO를 주로 사용한다.
도메인 객체를 View에 직접 전달할 수 있지만, 민감한 도메인 비즈니스 기능이 노출될 수 있으며 Model과 View 사이에 의존성이 생기기 때문!
(소규모 프로젝트는 DTO 사용이 불필요한 경우도 있다)

Entity만 사용할 경우

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를 넘겨줄 때 발생할 수 있는 문제점

  • 도메인 Model의 모든 속성이 외부에 노출됩니다.
    • UI 화면마다 사용하는 Model의 정보는 상이하지만, Model 객체는 UI에서 사용하지 않을 불필요한 데이터까지 보유하고 있습니다.
    • 비즈니스 로직 등 User의 민감한 정보가 외부에 노출되는 보안 문제와도 직결됩니다.
  • UI 계층에서 Model의 메서드를 호출하거나 상태를 변경시킬 위험이 존재합니다.
  • Model과 View가 강하게 결합되어, View의 요구사항 변화가 Model에 영향을 끼치기 쉽습니다.
    • 또한 User Entity의 속성이 변경되면, View가 전달받을 JSON 및 프론트엔드 Js 코드에도 변경을 유발하기 때문에 상호간 강하게 결합됩니다.

DTO도 사용할 경우

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 화면에서 사용하는 데이터만 선택적으로 보낼 수 있습니다.

Layered Architecture 상의 계층들에서


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);
}
  • View로부터 받아온 DTO를 Controller에서 Domain(Entity)으로 변환하고 Service 레이어에게 이를 전달하여 작업을 수행합니다.
  • Service 레이어는 Controller에게 Domain으 반환하고, Controller는 Domain을 DTO로 변환해 View에게 응답을 보냅니다.

    이때 드는 궁금증 : “꼭 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의 영속성을 관장

Repository 레이어는 Entity의 영속성을 관장하는 역할이라고 명시되어 있습니다. 이로 인해, 표현 계층에서 사용할 도메인 계층의 Aggregates를 DTO로 변환하는 작업을 Repository 단에서 책임지게 하는 것을 지양하자는 의견이 다수 존재했습니다.

실제로 이 글을 작성하면서 DTO와 Entity간의 변환과 관련된 여러 문서들을 참조했는데, 모두가 변환 로직을 Controller 혹은 Service 레이어에 위치시켰습니다. 그렇다면 DTO의 사용 범위 및 Entity간의 변환 위치는 Controller와 Service 중 어느 곳이 적합할까요? 🧐

Controller가 Entity를 DTO로 변환할 때 단점

  1. View에 반환할 필요가 없는 데이터까지 Domain 객체에 포함되어 Controller(표현 계층)까지 넘어옵니다.
  2. Controller가 여러 Domain 객체들의 정보를 조합해서 DTO를 생성해야 하는 경우, 결국 Service(응용 계층) 로직이 Controller에 포함되게 됩니다.
  3. 여러 Domain 객체들을 조회해야 하기 때문에 하나의 Controller가 의존하는 Service의 개수가 비대해집니다.
    => Service가 DTO를 반환시 이런 문제들이 상쇄된다!

결론...?

DTO-Entity 간의 변환 위치는 Service 레이어가 타당에 보인다.
하지만 Service레이어에 DTO가 드렁오지 않아야 여러 종류의 컨트롤러에서 해당 서비스를 사용할 수 있다.
하지만 현업을 하다보면 여러 종류의컨트롤러가 한 서비스를 사용하기 보단, 한 종류의 컨트롤러가 서비스를 쓰기 때문에 너무 엄격하게 제안할 필요도 없다

  • 서비스로 DTO 진입을 허용하되, 서비스 메소드 상위에서 DTO 체크 및 도메인 변환을 하고 변환 후에는 DTO는 사용하지 않고 도메인만 사용하도록 구현한다
  • 상황에 맞게 사용하자

참고
DTO의 사용 범위에 대하여

profile
개발자가 되고싶은 낭랑 24세

0개의 댓글

관련 채용 정보