DTO란, Data Transfer Objet
로 데이터 전송 객체
이다. 보통 개발시에 계층간에 도메인 전부가 아닌, 필요한 데이터만 모아서 Transfer
의 역할을 하는 객체이다.
이유를 설명하기에 앞서, DTO를 사용하지 않는 경우와 사용하는 경우를 비교해보자.
- Service 계층에서는 Member의 name 정보가 필요한 상황이다.
- 이제 Service 계층에서는 MemberRepository에 Member의 name을 요구하게 된다.
- MemberRepository는 DB에서 Member의 모든 정보를 담아 Member 객체를 Service 계층에 반환하게 된다.
- Service 계층에서는 반환 받은 Member 객체에서 name 필드를 get함으로써 name 정보를 얻는다.
- Service 계층에서는 Member의 name 정보가 필요한 상황이다.
- 이제 Service 계층에서는 MemberRepository에 Member의 name을 요구하게 된다.
- MemberRepository는 DB에서 Member의 name 객체(String)를 Service 계층에 반환하게 된다.
- Service 계층에서는 곧 바로 String type의 name 만을 반환받게 된다.
두 상황을 비교할 때, 객체지향
의 관점에서 바라보면 DTO를 사용하는 것이 바람직하다.
이 바람직한 이유들이 DTO를 사용하는게 좋은 이유가 될 것이다.
그 이유들은
물론 Member 객체에서 getName()을 통해 name 정보를 얻는 것은 크게 어색하지 않은 것 같다. 하지만 Member 객체를 가지고 있다면, getter를 통해 다른 필드에도 접근이 가능해진다. 이는 Service 계층의 메서드에서 관심사(Member의 name
)를 벗어나는 정보, 권한까지 전달 받게 된다고 생각하고, 두 객체 (Service, Repository)가 올바른 메시지를 주고 받고 있다고 생각하지 않는다.
Service 계층의 메서드는 Repository 계층의 메서드의 반환 타입(Member
)에 그대로
의존하고 있다. 다시 말해, 자신이 원하는 방식으로 반환 받고 있지 못 하다. 이렇게 다른 객체에 강한 의존성을 가지고 있다면, 올바른 객체간의 협력 관계라고 보기 힘들다. 추후에 변화에 대응하기도 힘들어 보인다.
사실 위의 두 가지 이유를 관통하는 핵심은 객체지향
이라는 생각이 든다.
객체는 캡슐화를 통해 자신의 필드로의 접근을 제한해야 하고
, 올바른 메시지를 주고 받음으로써 협력 관계를 구축해야 한다.
DTO에 대한 고민은 API 서버를 개발하며 줄곧 있었다. 객체지향
에 대해 제대로 신경쓰기 전에는 getter와 setter를 남발하고, 객체 간 강한 의존성은 신경쓰지 않았다.
가장 고민을 이끌었던 상황은
Controller
-Service
사이의 DTO이다.
Controller에서 RequestBody를 통해 클라이언트로부터 DTO를 입력받았다고 하자.
이 DTO를 토대로 Service 계층에서 로직을 수행하는 방법은
1) DTO를 그대로 넘겨준다.
2) 엔티티로 변환해 넘겨준다.
3) Service 계층을 위한 DTO로 변환해 넘겨준다.
세 가지로 생각이 든다. 결론부터 얘기하자면 3) 처럼 개발하는 것이 바람직하다고 생각한다.
각 상황에 대해 설명하자면,
이는 위에서 설명했던 대로, Controller - Service 사이에 강한 의존성이 생기게 되고, Service는 자신이 원하는 방식으로 정보를 제공 받지 못하게 된다.
Controlle가 클라이언트로부터 받은 DTO가 Member의 회원가입 DTO라고 한다면, 이를 토대로
Controller 계층에서 new Member(params);를 통해 Member 엔티티를 생성해 Service 계층에 넘겨줄 수 있다.
이 방법은 나쁘지 않다고 생각이 든다. 하지만, 완벽하지 않은(비영속인 상태) 엔티티가 파라미터로 넘겨지는 것이 매우 어색하다. 어쩌면 이 비영속 상태의 엔티티가 3)처럼 Service 계층이 원하는 방식의 DTO라고 생각이 들 수도 있지만, Controller 계층에서 도메인과 관련된 로직을 수행하는 것은 Controller의 역할을 넘어선다
고 생각한다.
이 그림이 가장 핵심을 설명하기에 첨부했다.
위처럼 각 계층마다 자신이 원하는 파라미터 / 반환 타입을 가지고 계층간, 객체간 메시지를 주고 받으며 협력하는 것이 바람직하다고 생각한다.
Controller
public void postMember(@RequestBody PostMemberReq postMemberReq) {
// PostMemberReq (Controller) -> MemberJoinParams (Service)
MemberJoinParams memberJoinParams = new MemberJoinParams(
postMemberReq.getName();
postMemberReq.getEmail();
);
memberService.join(memberJoinParams);
}
DTO 객체의 네이밍은 아직 어려운 것 같다...
나만의 규칙은
HttpMethod
+ Domain
+ Req/Res
Post
/Member
/Req
-> HttpMethod : Post (만든다.) / Member (멤버를) / Req : Request를 받는 DTO이외에는 구분 없이 도메인을 참고하는 편이다.
MemberJoinParams
: 멤버를 회원가입하는데에 필요한 파라미터들
EventSearchConditions
: 이벤트를 검색하는데에 필요한 조건들
https://techblog.woowahan.com/2711/
https://hudi.blog/data-transfer-object/