프로그래밍/Spring Boot

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

카프카뮈 2021. 2. 15. 20:26

바로 직전에, 스프링 부트에서 MVC 패턴을 구현하는 방법에 대해 간단한 글을 올렸다.

 

고민: SpringBoot에서의 MVC 패턴

이 글은 다음의 고민에서 시작한 글이다. 내가 스프링 부트로 지금까지 만든 프로젝트들은 MVC 패턴을 잘 따르고 있나? 각각의 계층은 어떤 역할을 수행하고, 어떻게 영향을 주고 받아야 하나? 스

kafcamus.tistory.com

그런데, 스프링 부트를 통해 MVC 패턴으로 기능을 구현하면서 늘 궁금했던 부분이 있다.

"컨트롤러(Web Layer)에서 서비스(Service Layer)로 값을 전달할 때,
무엇으로 전달해야 하나?"

1. 지금까지의 개발

지금까지의 진행 이전에는 우아한테크코스에서 학습한 내용 및 책에 있는 내용을 따라하다 보니,

컨트롤러가 뷰의 요청을 DTO로 매핑하고, 그 값을 다시 서비스에 넘기는 식으로 짰다.

이렇게 한 이유는 다음과 같다.

  1. 들어온 DTO를 엔티티로 변경해서 주면,
    컨트롤러에서 엔티티를 조작하여 값이 변조되거나 예기치 못한 오류가 생길 위험이 있다.
  2. 엔티티가 불완전한 경우가 존재한다.
    (예를 들어, 생성 요청이라면 아직 정해지지 않은 id 데이터는 요청에 들어있지 않을 것이다)
    이를 인자로 전달하는 것이 적절치 않다.
  3. 애초에 레이어 간 통신을 위해 쓰는 것이 DTO인데,
    서로 다른 레이어를 DTO를 활용하지 않고 엔티티를 직접 전달하는 것이 적절치 않다.

같은 이유로, 서비스에서 특정한 값을 반환할 때도 뷰-컨트롤러 간에 사용하는 Response DTO를 만들어 반환했다.

 

* DTO에 대한 개념을 자세히 보고 싶으시다면 다음 글을 추천드린다.

 

DTO란 무엇인가, VO와의 비교

오늘은 다음의 고민 때문에 글을 작성하게 되었다. DTO가 정확히 뭘 의미하는 거지? DTO를 꼭 써야하는 이유가 뭐지? DTO랑 VO를 많이 비교하던데, 뭐가 다른거지? DTO란 무엇인가 DTO(Data Transfer Object,

kafcamus.tistory.com

2. 고민의 시작

그런데, 구글링 도중 여러 블로그에서 컨트롤러에서 DTO를 엔티티로 바꿔 서비스에 전달하는 코드를 보게 되었다.

내가 생각하기엔 분명 불안정한데...한두 곳도 아니고 꽤 많은 곳에서 이런 식으로 구현을 하는 것을 보니 의심이 생겼다.

그래서 고민해 봤다. 왜 DTO 대신 엔티티를 직접 전달할까? 내가 생각한 이유는 다음과 같다.

  1. 만약 DTO로 전달하게 되면, 뷰 - 컨트롤러 - 서비스 간 통신에 같은 DTO가 사용되는 것 아닌가?
    그렇다면 뷰에서 가지는 요청이나 응답의 형태가 바뀜에 따라, 컨트롤러는 그렇다 치고 서비스까지 영향을 줄 수 있다.
  2. 같은 이유로, 서비스 레이어와 컨트롤러 레이어가 강한 의존을 가지게 된다.
    이로 인해 서비스 레이어의 모듈화도 어려워지고, 기능이 불어날수록 중복 코드가 늘어날 수 있다.

정리하면 다음과 같다 : 하나의 DTO로 쭉 전달하는 거, 컨트롤러와 서비스 간 의존을 강화하니까 안돼!

3. 도움을 받다

일단 위의 고민을 가지고, 현직 안드로이드 개발자인 친구에게 질문을 던졌다.

다행히 MVC 패턴을 활용한 개발 및 의존관계에 대한 수많은 디버깅 경험이 있던 친구라, 이에 대해 조언을 해줬다.

  1. 컨트롤러와 서비스의 의존은 매우 위험하다. 실제 개발 과정에서 이로 인해 애먹을 뻔한 경험이 있다.
  2. 의존 관계에 대해 더 공부해 본다면 앞으로 비슷한 고민이 생길때 쉽게 풀어낼 수 있을 것이다.
    SOLID 5원칙 중 의존관계 역전 원칙에 대해 공부해 보길 권한다.
 

의존관계 역전 원칙 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 객체 지향 프로그래밍에서 의존관계 역전 원칙은 소프트웨어 모듈들을 분리하는 특정 형식을 지칭한다. 이 원칙을 따르면, 상위 계층(정책 결정)이 하위 계층(

ko.wikipedia.org

역시 현업자의 경험이란 대단하다고 생각한다.

추가로, 친구는 이전에 사용했던 해결법 하나를 공유해주었다.

컨트롤러와 서비스 간 통신을 할 때,
컨트롤러가 뷰와 통신할 때 사용한 DTO를 그대로 사용하면 강한 의존이 생겨 위험하다.

그러나 DTO를 엔티티로 바로 바꿔서 전달하는 것 역시 적절하지 않을 수 있다.
만약 DTO 안에 toEntity 메서드를 만들거나, 엔티티 안에 toDto 메서드를 만들어서 변환을 한다면
둘 중 하나가 바뀌어도 서로를 수정해야 하고, 결과적으로는 컨트롤러와 모델(서비스)의 의존을 만들게 된다.

이를 해결하기 위해, Mapper라는 컨트롤러에 종속적인 클래스를 만든다.
이 클래스는 정적 유틸 클래스로, DTO를 엔티티로, 혹은 반대로 변환시켜주는 기능을 가지고 있다.
Mapper를 사용하면 DTO와 엔티티는 서로를 모를 수 있고, 한쪽에 수정이 발생해도 Mapper만 고치면 된다.

매우 좋은 방법이라는 생각이 들었다.

 

위 해결책의 장점은, DTO와 엔티티 간의 의존까지 고민했다는 것이다.

나도 이에 대해 고민하게 되어, 습관처럼 DTO에 넣던 toEntity 메서드에 대해 다시 한 번 생각해 보게 되었다.

 

다만 단점이라면, 아직도 컨트롤러에서 엔티티에 접근할 수 있는 것은 같으니 불안하다는 것.

만약 몇 년 뒤, 유지보수 과정에서 이 코드에 대해 잘 모르는 개발자가 변환된 엔티티를 조금만 잘못 건드린다면?

이런 생각을 하게 된다.

또한, 위에서도 언급했듯 레이어 간 통신인데 DTO를 쓰지 않는다는 찜찜함이 마음 한구석에 남는다.

4. 좋은 글을 찾다

때마침 큰 행운이!! 배민 블로그에 나와 같은 고민을 가지는 글이 올라왔다.

우아한테크코스를 같이 진행한 크루가 쓴 글인데, 못본 사이 엄청난 성장을 한 게 보여서 부럽고 그렇다.

게시글의 링크는 다음과 같다.

 

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

안녕하세요!!!

woowabros.github.io

본문의 중간에서, Controller와 Service 레이어의 강한 결합이라는 제목으로 다루고 있는 내용을 읽어보자.

이 글에서도 역시 엔티티를 바로 전달하는 것의 위험성에 대해 고민하고 있고,

한편으로는 같은 DTO를 서비스에 그대로 전달할 때의 문제점에 대해서도 잘 설명해 주고 있다.

(특히 외부 서버와의 통신에 대해서는 생각도 못했다)

이 글에서 다루는 중요한 요점이 하나 있다.

서비스에서 원하는 포맷과 컨트롤러에서 원하는 포맷이 다를 수 있다!

5. 결론

위의 기술 블로그 글에서는 결론으로,

DTO를 하나 더 만들어 컨트롤러-서비스 통신 간에 사용하는 것(추후 서비스 DTO라 호칭)을 제시한다.

 

처음엔 이 방안을 들었을 때 공감하기 어려웠다.

왜냐하면, 서비스와 컨트롤러가 원하는 포맷이 다른 경우가 생각만큼 많지 않으니까.

지금까지의 개발에서는 뷰-컨트롤러 간에 사용한 DTO를 그대로 줘도 아무 문제가 없었는데,

원래의 DTO 코드와 중복되는 서비스 DTO를 또 만드는 것이 낭비라는 생각이 들었기 때문이다.

 

다만 이에 대해 주변에 의견을 구하고 토론해 본 결과, 나온 공통의 의견이 있다.

중복되는 코드가 존재하고 그것을 만드는데 시간을 쓰는 것은 낭비지만,

만약 서비스와 컨트롤러에서 원하는 값이 달라졌을 때 유지보수하는 비용을 생각하면

오히려 저렴한 비용으로 문제의 여지를 막는다는 것!!

이 이야기를 듣고 나서 나도 서비스 DTO의 사용에 대해 납득하게 되었다.


길게 고민해 보았고 주변 개발자들과 토론도 해봤지만, 아직 100%의 확신을 가지고 있지는 않다.

그러나 개발에 100% 하지 말아야 할 것, 100% 해야 할 것이 몇 개나 있을까? 

 

정답을 찾아서 바로 적용하기보다, 이유를 찾고 더 좋은 코드를 만들어가는 과정을 즐기고자 한다.

 

사적인 의견이 많습니다. 잘못된 내용이 있다면 지적 부탁드립니다.

반응형