[스프링/Spring] DTO는 왜 써야 하나?

kindof

·

2021. 9. 7. 12:52

1. 고민?

DTO(Data Transfer Object)란 계층간 데이터 교환을 위해 사용하는 객체입니다.

아래 코드를 보면서 DTO는 왜 필요한가에 대해 생각해보겠습니다.

* 실제 간단한 엔티티 설계의 일부입니다.

1. Entity
2. Service
3. Dto
4. Controller

위 예제 코드는 Posts라는 엔티티를 생성하는 코드입니다.

맨 처음에 PostsSaveReqeustDto라는 Dto 객체를 빌더 패턴으로 생성하고 Dto를 컨트롤러에게 던져줍니다.

컨트롤러는 해당 Dto를 다시 Service에게 넘겨주고, Service 딴에서 JPA를 이용하여 생성된 객체를 저장합니다.

"그런데 DTO와 Entity는 거의 똑같은 것 같은데, 왜 변환을 해서 사용해야 하며, 그렇다면 언제 DTO가 필요할까요?"



2. 언제, 왜 DTO를?

몇 가지 책과 기술 블로그를 참고하여 제가 이해한 범위에서 이 고민에 대한 답을 몇가지 항목으로 작성해보겠습니다.

 

잊을만 하면 돌아오는 정산 신병들 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요!!! 안녕하세요. 정산시스템팀 온택트 신입 개발자 김시영입니다. (입사한 지 2달이 되었지만 한 번도 출근한 적이 없습니다..) 꿈꾸던 회사에서 (방에서) 직접 글을 작성하

techblog.woowahan.com

 

DTO에 관한 생각

과거 회사에서 DTO를 사용하지 않고 Entity로 통신을 주고 받는 경우가 있었다.. 결국 대참사가 벌어졌고 DTO를 도입하게 되었던적이 있다.대부분의 Entity 클래스들은 대부분 DB 테이블 스키마와 1:1

velog.io


1) Entity클래스와 거의 유사한 형태임에도 DTO 클래스를 추가로 생성하는 이유는 Entity 클래스가 데이터베이스와 맞닿은 핵심 클래스이기 때문입니다.

Entity 클래스를 기준으로 테이블이 생성되고 스키마가 변경되는데, 화면 변경과 같은 사소한 기능 변경을 위해 테이블과 연결된 Entity 클래스를 변경하는 것이 너무나 큰 변경이 되는 것이죠.

또한 View Layer와 DB Layer 역할 분리를 철저히 하는 것이 좋은데, 실제로 Controller에서 결과값으로 여러 테이블을 조인해서 줘야 할 경우가 빈번하기 때문에 Entity 클래스만으로 표현하기 어려운 경우가 많습니다.

2) 다양한 비즈니스 로직과 요구사항에 대해 유연하게 대응할 수 있습니다.

파라미터로 엔티티 자체를 받게 되면 엔티티에서 정해진 포맷에 맞춰 개발을 해야합니다. 하지만 DTO는 각 비즈니스 로직에 맞춘 필드들만 생성함으로써 DTO를 보면 어떤 값들이 매핑되는지 쉽게 파악할 수 있고, 만약 API 설계 상황에서 필드에 다른 이름을 부여하거나 하는 상황에서도 유연하게 대처할 수 있습니다.

3) Controller와 Service 사이에서 강한 의존을 방지하기 위해서 DTO를 사용합니다.
Service가 받고 싶은 파라미터가 Controller에게 종속적이게 되면 Service가 Controller 패키지에게 의존하게 됩니다. 따라서 이를 방지하기 위해서라도 Service가 원하는 포맷에 맞춰 Controller 딴에서 DTO를 통해 그 포맷을 맞춰주는 것입니다.

4) Service 레이어가 모듈로 분리되어버리면 해당 Type을 쓸 수 없게 됩니다.

5) 트랜잭션으로 처리되어야 하는 DTO 항목이, 항상 요청으로 들어온 결과값과 동일하지 않을 수 있습니다. 아래 그림을 예로 들어보겠습니다.


위 그림에서 사용자 요청의 파라미터를 통해 외부 API를 여러번 호출한 이후 Service 레이어를 호출하는 경우, Controller가 받은 Web DTO와 Service가 받아야 할 DTO가 달라집니다.

 

외부 API 호출뿐만 아니라 Client 요청 이후 Service 레이어를 호출하기 전 다른 작업으로 인해 데이터 포맷이 달라질 수도 있겠죠.

이런 경우에 Service 레이어가 Controller 레이어 DTO에 의존하고 있다면 문제가 될 수 있습니다. 따라서 Service 레이어는 자신이 원하는 포맷으로 데이터를 받을 수 있어야합니다.

참고로 위의 예제에서 Controller에서(Controller-Service사이에 중간 레이어를 두고 하는 경우 포함) 외부 API를 조회하는 이유는 Service에서 해당 작업을 수행하는 경우 트랜잭션과 무관한 작업이 트랜잭션내에 포함되기 때문에 DB 타임아웃과 같은 이슈가 발생할 수 있기 때문입니다.

이러한 문제를 해결하기 위해서는 Service는 자신이 원하는 포맷에 맞게 데이터를 받고, Controller에서 그 포맷을 만들어주는 것이 필요합니다.

 

하지만...


여러 글들을 읽어보면서 DTO를 어느 영역에서 어느정도로 사용해야 하는가에 대한 대답은 여러가지였던 것 같습니다.

특히 'DTO의 사용 목적은 클라이언트와 Controller layer 간 중계만 담당하는 것이기 때문에 Service layer로 넘어가는 객체는 DTO가 아닌 Entity 객체여야 한다.'는 주장도 있었습니다.

하지만 제 생각으로는 서비스 로직에서 어떤 작업을 할 때 Entity가 2개 이상 필요하다고 하면, Controller에서 여러 Entity들을 한 데 모은 DTO를 서비스에 넘겨주면 되기 때문에 컨트롤러가 서비스에 의존적이지 않게 될 수 있다는 장점이 있다고 생각합니다.

또한, DTO를 서비스에 넘기지 않으면 Response에 포함될 필요가 없는 필드까지 Controller에 다시 ResponseDto로 돌아가게 된다는 점에서 문제가 생긴다고 생각합니다.

결론적으로 정리하자면, 서비스는 자신이 원하는 포맷을 기다리고 있으면 되고 컨트롤러에서 DTO를 생성하여 서비스에게 넘겨주면 된다고 생각합니다. 그리고 서비스는 해당 DTO를 비즈니스 로직을 거쳐 ResponseDto를 다시 던져주면 되겠죠.

 

3. 정리

DTO에 대한 개념이나 사용 방법에 대해서 정말 많은 글이 있었습니다.

지금 제가 이해한 DTO는 정말 얕은 수준이라고 생각합니다. 그래도 "그냥 그렇게 쓰는가보다~"하고 지나가는 것보다 궁금한 것에 대해 한 번 정리해보고 고민해보는게 의미있는 것 같습니다.

감사합니다.