인프런 커뮤니티 질문&답변

Jon님의 프로필 이미지
Jon

작성한 질문수

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

주문 조회 V3.1: 엔티티를 DTO로 변환 - 페이징과 한계 돌파

Dto 사용시기에 대한 질문

작성

·

8K

27

안녕하세요. 항상 강의 잘 듣고있습니다 ! 

질문이 두가지 있습니다.

첫째, "어느 레이어에서 DTO로 반환하는가?" 입니다.

현재 강의에서는 controller 에서 repository 를 바로 di 해서 사용하고 있으므로 서비스 레이어가 존재하지않는 것 같습니다.

하지만 만약 서비스레이어가 존재한다면, 지금 컨트롤러에서 작업되고있는 dto 변환 로직이 서비스에 들어가는것인가요.

아니면 서비스까지는 entity 를 유지한채 controller 에서 dto 로 변환되는 로직이 들어가는걸까요?

(물론 정답이 없는 아키텍쳐링에 대한 질문입니다만, 대체적으로 어떻게 사용하고있는지 그리고 영한님의 노하우에 대해 궁금합니다. 구글링과 여러 블로그들을 보면 보통 controller 가 아닌 service 에서 dto 를 변환하는것이 낫다고 하여 혼란이옵니다.)

둘째, "Create, Update같은 것에선 언제 DTO로 반환하는가?" 입니다.

흔히 서비스쪽에서 dto 를 변환한다고 하니, 이것을 가정하고 아래와같은 코드를 작성해봤습니다.

[#1] Entity

class XXEntity {

 String id,

 Sting name

}
class YYEntity {

 String id,

 Sting title,

 XXEntity xx

}

[#2] Repository

SampleRepository 는 JPA 레포지토리

XXRepository, YYRepository

[#3] Service

XXService 는 XXRepository를 DI 해서 비지니스로직을 돌려 나온 Entity를 Dto를 반환한다.

YYService 는 YYRepository를 DI 해서 비지니스로직을 돌려 나온 Entity를 Dto를 반환한다.

[#4] Controller

SampleController 의 createXY

DI : XXService

DI : YYService

xId 를 이용하여 XXService에서 찾은 A를 

YYService의 create 에 넣어 YY를 만들어 리턴한다.

XXDto found = XXService.findById(id)

XX foundToEntity = XXDto.toEntity() // <-- 서비스레이어에서 dto 를 반환할때, 이부분이 너무 불편합니다.

YYDto saved = YYService.create(new YYY("a", "b", foundToEntity))

return saved

보통 ****** 에 XX의 entitiy가 들어가는데 만약 서비스로직에서 dto 로 변환해서 내려주고있다면,

현재 XX에서는 dto를 받고있으니 이걸  다시 entitiy방식으로 변환시켜서 넣어주어야 하는데, 

이것이 매우 불편하고 왠지 이런 방식으로 하는게 아닌것같아서요. 이때도 마찬가지로 service쪽까지는 entity로 유지하다가, controller 에서 변환을 시켜야되는것인지 궁금합니다.

<추가>

저는 지금 혼란이 오는 것이 DTO 란 무엇인가입니다.
"외부에 entity를 노출시키면안되기 때문에 dto 로 변환해야한다" 라는 것도 있지만, 레이어간 데이터 이동을 위해서 만들어진것이 dto이기도 하니 어떤측면에서 바라보며 사용해야할지 혼란이 옵니다.  

도움을 부탁드립니다 !!

답변 2

42

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. Jon님 좋은 질문입니다.

먼저 DTO를 간단하게 생각해야 합니다.

DTO는 단순히 계층간 데이터를 전달할 때 사용하지만, 그것이 필수는 아닙니다.

물론 레이어간 데이터 이동이 필요하면 DTO를 이동해도 되지만, Entity를 이용하셔도 되고, 단순히 String, Map등을 이용해도 됩니다.

특히 다음과 같은 로직은 의존관계 관점에서 문제가 있습니다.

XXDto found = XXService.findById(id)

XX foundToEntity = XXDto.toEntity() // <-- 서비스레이어에서 dto 를 반환할때, 이부분이 너무 불편합니다.

정말 중요한 것은 의존관계라는 관점이 중요합니다.

이 코드는 컨트롤러에서 XXService도 의존하고, XXDto도 의존하고, XX Entity도 의존합니다. 결과적으로 컨트롤러가 모든 곳에 의존하는 좋지 않은 상황입니다.

차라리 XXEntity entity = XXService.findById(id)라고 하면 컨트롤러가 XXService와 XXEntity에만 의존하기 때문에 XXDto에 대한 의존관계 부담이 줄어듭니다.

또는 계층을 확실하게 분리해서 컨트롤러가 엔티티 계층에 의존을 하지 않겠다 라는 목적이 있다면

컨트롤러가 XXService, XXDto에만 의존하도록 만들어야 합니다.

여러가지 방법이 있지만 실용적인 개발 아키텍처는 컨트롤러, 서비스, 리포지토리 계층이 모두 엔티티 계층에 의존하는 것입니다. 왜냐하면 엔티티라는 것이 우리의 핵심 비즈니스이기 때문에 대부분의 로직은 엔티티가 필요합니다.

물론 여기에서 기술적 문제든, 아키텍처 관점이든 필요에 따라서 엔티티를 어느 계층까지 노출해야 하는가는 또 다른 고민이 필요합니다.

추가로 DTO의 위치는 저는 해당 DTO를 생성하는 곳에 있어야 하는게 좋다 생각합니다.

예를 들어서 DTO를 리포지토리에서 생성하면 해당 리포지토리와 같은 패키지에 DTO가 있어야 패키지 의존관계가 안전하게 유지됩니다.

DTO가 XXService에서 만들어진다면 XXService와 같은 패키지에 있는게 좋다 생각합니다.

그런데 보통 실수하는 것이 DTO를 서비스와 같은 계층에 두고, 리포지토리에서 해당 DTO를 사용하는 문제 입니다. 이렇게 되면 패키지 의존관계가 양방향이 됩니다. 서비스는 리포지토리가 필요하고, 리포지토리는 서비스가 제공하는 DTO가 필요한 문제이지요.

정리하면

1. 실용적으로 엔티티를 전체 구조에서 사용하자(물론 아키텍처 방향에 따라서 엔티티 노출은 제약할 수 있다)

2. DTO를 단순한 객체로 생각하고, DTO의 위치는 의존관계 + 패키지 의존관계를 고민하자.(특히 순환 참조를 조심하자)

감사합니다.

저도 이게 궁금했는데, 좋은 의견 감사합니다. 말씀 중에 궁금한 점이 있는데,  저는 도메인별로 패키지를 나누고 해당 패키지 안에, controller, service, repository, dto 이렇게 다시 패키지를 구분합니다. 영한님은 말씀대로라면 dto 패키지는 따로 안 만들고, 해당 dto를 사용하는 패키지 안에 dto 파일을 넣는 게 좋다고 생각하시는 건가요? 예를 들어, 회원 가입에 필요한 dto인 signUpDto 클래스는 controller 패키지에 넣어야 하는 건가요?

김영한님의 프로필 이미지
김영한
지식공유자

더 자세히 구분하기 위해서 내부에 dto 패키지를 만드셔도 무방합니다.

감사합니다.

6

Jon님의 프로필 이미지
Jon
질문자

자세한답변감사합니다 !! 참고하여 개발하겠습니다 ^_^ 

Jon님의 프로필 이미지
Jon

작성한 질문수

질문하기