[Spring Boot] DTO 사용에 대한 고민

jin·2023년 1월 2일
0

Spring Boot

목록 보기
4/8

간단한 프로젝트를 해보면서 DTO를 모든 요청마다 생성해야 하는지 고민이 되었다!

DTO 란?

  • DTO(Data Transfer Object)란 계층간 데이터 교환을 위해 사용하는 객체(Java Beans)이다.
  • 계층간 이동(Service <-> Controller), 요청(요청 Body <-> Controller 내 핸들러) 에 사용된다. 계층 간 이동에는 로직을 가지고 있지 않은 순수한 데이터 객체로, 불변성을 유지하기 위해 Setter 사용을 지양해야 한다.
    요청에 필요한 DTO는 Entity간 객체 매핑을 통해 Entity로 변환하는 경우가 많은데 Builder패턴을 이용하여 사용도 가능하다.

DTO를 사용해야 하는 이유

  1. 필요한 데이터만 응답으로 줄 수 있다.
    • 클라이언트마다 넘겨줘야 하는 정보는 API마다 상이
    • Entity 자체를 클라이언트에게 응답으로 넘기는 것을 불필요한 데이터를 포함하거나, 민감한 정보 노출 등의 문제 발생
  2. 순환 참조 문제
    • JPA로 개발할 때, 양방향 참조된 엔티티를 Controller에서 응답으로 return하게 되면, 엔티티가 참조하고 있는 객체는 지연 로딩되고, 로딩된 객체는 또 다시 본인이 참조하고 있는 객체를 호출하게 된다. 물론 이러한 순환참조의 원인은 양방향 매핑 자체에 있다고 볼 수 있지만, 응답의 return으로 DTO로 두는 것이 더 안전하다.
  3. Entity 구현을 캡슐화하여 보호할 수 있다.
    • DTO 가 없다면 클라이언트의 요청과 Entity Model 이 강하게 결합되어 클라이언트의 요구사항 변화가 Entity 에 영향을 끼치기 쉽다.
    • Entity 는 도메인의 핵심 로직, 속성을 갖고 있으며, 실제 DB 테이블에 대응되는 클래스이므로 함부로 변경되지 않도록 보호되어야 한다.
  4. Domain 객체에 바로 접근하지 않기 위함(테이블에 직접적으로 접근하지 않음으로써, 데이터 보호)

첫 번째 고민

DTO를 상황마다 생성하는 것?!

클래스를 여러개 만들더라도, 코드가 중복되는 것처럼 보일지라도, 명확한 것이 훨씬 나은 선택이라는 김영한님의 답변을 봤다. 기본 공통 DTO를 만들고 이걸로 해결이 안되는 특수한 경우에 한해서 별도의 DTO를 생성하는 걸 선호하신다고 한다.

  • 처음 프로젝트를 진행하면서 목적에 상관없이 같은 요청이나 응답을 줘야할 때 동일 DTO를 사용하였는데 다른 분들은 목적에 따라 DTO를 분리하는 걸 보고 DTO에 대해 고민하게 되었다. 여러 글을 찾아보니 다들 나와 같은 고민끝에 목적에 맞게 분리하는 방법을 선택하는 것 같았다.
  • 이렇게 분리하게 되면 수정이 일어나도 다른 API의 요청이나 응답에 영향이 가지 않는다는 장점이 있지만 클래스가 너무 많아져서 관리가 안될 수도 있다는 단점이 있다.

💡 해결 방안
클래스가 많아지는 문제를 DTO 내부에 InnerClass를 만들어 관리하는 방법으로 해결할 수 있다. 해당 클래스 안에서만 한정적으로 사용한다는 의미를 부여할 수 있어 개발자 입장에서 신경서야 하는 외부 클래스들이 줄어드는 효과가 있다.
그러나 필드가 많다면 InnerClass를 사용하는 것은 클래스가 비대해보일 수 있어 주어진 상황에 알맞게 사용하는 게 좋다.

🤔 toDto와 toEntity 로직은 어느 위치에 만드는 것이 좋을까?
클린 아키텍처 기준 DTO 파일 안에 toEntity, fromEntity를 다 만들라고 한다.
의존성을 한 방향으로 하기 위함이다. 별개 파일에서 toEntity, toDto를 구현할 경우 Entity도 Dto를 의존하고 Dto도 Entity를 의존하는 순환 사이클이 생기게 된다.


두 번째 고민

Entity, DTO 간 변환은 어디서 처리하는가?

지금까지 큰 고민없이 Entity와 DTO를 변환하여 사용하였는데 이번에 찾아보면서 많은 사람들이 Controller와 Service 중 어디서 변환 처리를 해야 하는지에 대한 질문이 엄청 많았다.
지금까지 수업한 내용으로는 Service에서 변환처리를 해주었는데 다양한 의견을 알아볼 수 있었다.

  1. Controller Layer에서 변환작업

    Controller Layer에서 변환작업이 이루어지면 Controller Layer의 코드가 복잡해지고, 비즈니스 로직이 포함되게 된다. 그러면 Controller Layer는 웹 계층을 처리하기 위한 로직만 존재하고, Service Layer는 주된 비즈니스 로직을 처리해야 되는 구조가 위배될 수도 있다.
    하지만 Service Layer에서 Entity를 바로 받게 함으로써, Service Layer는 Entity에만 의존하기 때문에 재사용성이 높아진다는 장점이 있다.
  1. Service Layer에서 변환작업

    Service Layer에 들어갈 때부터 DTO로 들어가며, DTO로 반환된다. 다양한 DTO들이 전부다 Service Layer로 모이기 때문에 비대한 Service Layer가 나올수 있는 설계적 리스크가 존재한다. 또한 Service Layer가 특정 DTO를 의존하기 때문에 여러 종류의 Cotnroller에서 해당 서비스를 사용할 수 없어져 코드의 재사용성이 낮아진다. 그리고 Entity의 경우 Transaction을 벗어나게 되면 JPA를 통한 Entity-DB 연결이 끊기되는 문제가 있다.
    하지만 Controller Layer에서의 비즈니스 로직의 문제를 해결해주며, Controller는 Entity에 알지 못하고 오직 DTO에 대해서만 알기 때문에 도메인을 보호한다.

  2. ServiceDTO와 Mapper 분리

    Mapper가 중간다리를 해주어 Cotroller, Service 계층이 완전히 분리되는 효과를 얻을 수 있다.


깊어진 고민..

지금까지의 DTO 사용

  • Controlelr -> Service 흐름에서 동일 Dto를 그대로 받아서 사용하였다. 이렇게되면 Service가 받고 싶은 포맷이 Controller에 종속적이게 되는 문제가 있다. 예로 Client 요청 이후 Service 레이어를 호출하기 전 다른 작업으로 인해 포맷이 달라질 수 있는데 위와 같이 Service가 Controller에 의존하고 있다면 문제가 될 수 있다.

  • 그래서 위과 같이 Service 자신이 원하는 포맷에 맞게 데이터를 받고 Cotnroller에서 그 포맷을 만들어주는 것이 적절하다고 한다.

정리

  • 프로젝트 규모, 아키텍쳐의 방향 등을 종합적으로 고려해보고 나의 상황에 맞게 쓰는 게 중요한 것 같다

참고자료

Controller에서 Service에 값을 어떻게 전달할까?

[Spring boot] DTO <-> Entity 간 변환, 어느 Layer에서 하는게 좋을까?

잊을만 하면 돌아오는 정산 신병들

Spring/Coding Convention DTO 를 사용해야 하는 이유

요청과 응답으로 엔티티(Entity) 대신 DTO를 사용하자

profile
jin

0개의 댓글