Dto와 Entity의 변환에 대한 고민과 Mapper의 사용

jihunnit·2023년 2월 1일
0

Spring을 사용하여 백엔드 개발을 하다 보면 자연스럽게

Controller -> Service -> Repository -> DB

의 계층 구조로 구성을 하게 될 것이다.

그렇게 개발을 하다 보면 이제
Controller 계층에서 api로 직렬화해서 반환할 때
DTO에 대해 생각하게 될 것이다.

왜냐하면, 우리가 그 객체 자체를 직렬화해서
그 객체가 가진 모든 프로퍼티를 반환하여 보여주게 된다면
문제가 생길수도 있기 때문이다.

이렇게 가정을 해보자
회원을 의미하는 Member Entity가 존재한다.
이 회원은 로그인을 가입에 사용한 이메일과 비밀번호를 통해 한다.

@Entity
public class Member{
	...
    private String Email;
    private String password;
	...
}

그런데, 우리가 로그인한 회원의 정보를 가져오는 마이페이지에
그대로 로그인된 Member Entity를 반환하게 한다면?
api에 우리가 보호해야 할 password 가 그대로 반환될것이다.
이는 매우 위험하다. (설령 프론트에서 그걸 안쓴다고 하더라도 api에서 data 형태로 저걸 응답한다는것 자체가 위험하다. 탈취당하거나 한다면..)

그렇기에 우리는 DTO(Data Transfer Object)를 이용하여
실제로 필요한 데이터만 따로 뽑아서 저장한다.
실제로, 여러분이 어떤 커뮤니티의 마이 페이지에 접속하게 되면
보통 이런식일 것이다.

...
이름 : 지훈
email : jihoon806@gmail.com
비밀번호 : (비워져 있거나 뭔가로 채워져 있거나..)
...

이처럼, 제공해야 할 정보와 그렇지 않을 정보를 나눠주는 역할을 한다고 보면 된다.

이러한 DTO는 이렇게 응답에도 쓰이고, 생성을 하는 요청의 경우에도 존재할 수 있다.
DTO -> Entity 혹은 Entity -> DTO의 변환은 고민거리 중 하나이다.

그래서 어디서 변환을 하는가?
적어도 나의 탐색 결과 특별한 상황을 제외하고 일반적으로는 위에서 언급한 세 계층, Controller, Service와 Repository 중에서 Repository에서의 변환을 언급하는 사람은 거의 없었다.

대부분
1. Controller에서 변환을 한다. POST Request 시에 받은 DTO를 Entity로 변환을 해서 Service에 넘겨주고, GET 요청 시에는 Service가 Entity를 넘겨주면 이를 받아서 Controller에서 DTO로 변환을 하고 반환을 한다.

2. Controller는 POST Request에서 받은 dto를 아무 작업도 하지 않은 채 Service에 넘겨준다. 그러면 Service에서 dto의 변환 작업을 모두 수행한다. GET 요청 시에도 Service에서 변환 작업까지 수행한 후 Controller에게 DTO를 넘겨준다.

정도로 정리되는 것 같다.

1번과 2번은 각각의 장점이 있다.

1번은 Service에서 사용한 로직의 재활용성을 높인다는 장점이 있지만, Controller가 Domain과 결합하여 Domain의 변경 시 Controller가 변경 될 가능성이 존재하게 된다.

2번의 경우에는, Domain의 변경으로 인해 생기는 문제점을 Controller까지 전파하지는 않으나, 반대로 Controller와 Service의 결합도가 높아지게 된다.

이전에 프로젝트를 진행할 때에도 이것과 비슷한 고민을 골랐고,
나는 1번을 선택하여 프로젝트를 진행했었다.
왜냐하면, 적어도 당시의 내가 생각하기에는, 기껏 객체 지향을 위해 의존성 주입을 하며 느슨한 결합 상태를 만들어 놓은 상황에서, 2번의 case를 통해 Controller와 Service의 결합도를 높이는 것이 바람직하지 않다고 느꼈기 때문이다.

그래서 나는 저번에 진행한 프로젝트 WEATO의 경우에는, Controller에서 모든 변환 과정을 거치게 개발하였다.

그리고 시간이 지난 후
다음 프로젝트를 위한 구상을 하면서도
역시 이 문제를 고민하게 되었는데

이번에는 Mapper의 존재에 대해 알게 되었다.
Mapper를 Controller와 Service 사이에 위치시키면서,
이 둘의 변환에 대해서는 Mapper가 전담하게 하고

Service에게 엔티티를 전달해 줄 때도
Controller가 받은 dto를 Mapper가 엔티티로 변환한 뒤 전달하고,

Controller가 dto를 받을 때도 Service에서 준 Entity를 Mapper가 가져가서 dto로 바로 변환하는 방향으로
Controller에서 Entity에 대한 의존성을 제거하였다.
또한, Service 또한 Controller에서 생성(혹은 받았다던지)한 DTO에게 의존하지 않게 되면서 둘 사이의 의존성을 제거하였다.
가령 이런 코드를

@GetMapping("/posts/{id}") //게시글 단건 조회
public PostRetrieveDto retrievePost(@PathVariable("id") UUID id){
	Post post = pst 	       
}
@GetMapping("/posts/{id}") //게시글 단건 조회
public PostRetrieveDto retrievePost(@PathVariable("id") UUID id){
        return postMapper.
                createRetrieveDtoFromPost(postService.getOnePost(id));
    }

이런 식으로 바꿔줌으로써
PostController 에서는 import ...Post; 없이
post를 의존하지 않게 코드를 짤 수 있게 되었다!(아마)

물론 DTO의 변환과 위치의 적합성이라는건
정답이라는게 없다.
사실 개발의 모든 부분이 그렇다.
여러가지 요소들이 각자의 효용과 단점을 지니고 있다.

상황에 따른 적정기술을 사용하는것이 가장 중요하겠다!

profile
인간은 노력하는 한 방황한다

0개의 댓글