Controller와 Service 계층 간의 데이터 전달
학부때 했던 프로젝트를 리팩토링 하던 중에 의문이 생겼다. Spring MVC 패턴에서 계층 간 데이터 전달은 DTO로 한다고 배웠다.
Controller-(DTO)-Service-(DTO)-Repository(DAO)-(Entity)-DB
그런데 강의에서 배웠던 코드에서 Service 로직은 Entity를 반환한다.
문제는 이 Entity를 DTO로 변환하여 Controller에 전달해야 하는건지, 아니면 같은 DTO로 주고 받아야 하는 게 더 편한 것 같은데 위험할 것 같기도 하다(찾아보니 실제로 위험하다).
그렇다면 전달용 DTO를 각각 따로 만들어야하나?
도대체 해결책이 뭘까 싶어서 구글링을 했더니 이미 훌륭하신 분들이 열심히 포스팅한 글이 있었다! 그것들을 참고하여 정리해보았다.
1. DTO를 엔티티로 바로 바꿔서 전달하는 것의 위험성
- DTO 안에 toEntity 메서드를 만들거나, 엔티티 안에 toDto 메서드를 만들어서 변환 시
- 둘 중 하나가 바뀌어도 서로를 수정해야 한다.
- 결과적으로 컨트롤러와 모델(서비스)의 의존을 만들게 된다.
2. 같은 DTO를 서비스에 그대로 전달할 때의 문제점
하나의 DTO로 쭉 전달하면, 컨트롤러와 서비스간 의존성이 높아진다. 즉 Service가 받고 싶은 포맷(Parameter)이 Controller에 종속적이게 된다. 그러면 다음 경우에 아주 위험해진다.
경우1) Service레이어가 모듈로 분리되는 경우 해당 Type을 사용할 수 없다.
경우2) 서비스에서 원하는 포맷과 컨트롤러에서 원하는 포맷이 다를 수 있다.
이 경우, Service 레이어가 Controller 레이어 DTO에 의존하고 있다면 문제가 될 수 있다.
- 트랜잭션으로 처리되어야하는 DTO 항목이, 항상 요청으로 들어온 값과 동일하지 않을 수 있다.
- 수행되는 Controller 로직에 따라 데이터 포맷이 달라질 수 있다(Controller가 받은 Web DTO와 Service가 받아야 할 DTO가 달라진다)
- 사용자 요청의 파라미터를 통해 외부 API를 호출할 시
- 다른 작업을 한 후 Service 레이어를 호출할 시
참고: Service에서 API 호출 등의 작업을 수행하는 경우 트랜잭션과 무관한 작업이 트랜잭션내에 포함되기 때문에 DB 타임아웃과 같은 이슈가 발생할 수 있음. 따라서 Controlle에서 로직 작성
결론
방법1) Mapping을 지원해주는 라이브러리 사용
- 컨트롤러에 종속적인 Mapper 클래스를 만든다.
- 정적 유틸 클래스
- DTO를 엔티티로, 혹은 반대로 변환시켜주는 기능을 가지고 있다.
- 예) MapStruct 라이브러리 참고하면 좋을 블로그
- Mapper를 사용하면 DTO와 엔티티는 서로를 모를 수 있고, 한쪽에 수정이 발생해도 Mapper만 고치면 된다.
- 여전히 컨트롤러에서 엔티티에 접근할 수 있으므로, 유지보수 과정에서 위험성이 존재.
- 레이어 간 통신인데 DTO를 쓰지 않는다
방법2) 서비스 DTO를 따로 생성
- Service 레이어가 자신이 원하는 포맷에 맞게 변환된 데이터를 전달 받도록, 서비스 DTO를 따로 생성하여 컨트롤러-서비스 통신 간에 사용
일단 두 방법 모두 써봐야 할 것 같다. 현재 리팩토링 중인 프로젝트는 규모가 크지도 않고, 먼저 MapStruct 라이브러리를 사용해봐야겠다.
참고한 글
https://kafcamus.tistory.com/12
https://techblog.woowahan.com/2711/
https://unluckyjung.github.io/dev/2021/11/20/Dto-Entity-Mapper/