Springboot) Controller, Service, Repository 계층 간의 DTO

60jong·2023년 1월 27일
1

Spring

목록 보기
4/9
post-thumbnail

DTO

DTO란, Data Transfer Objet데이터 전송 객체이다. 보통 개발시에 계층간에 도메인 전부가 아닌, 필요한 데이터만 모아서 Transfer의 역할을 하는 객체이다.

DTO를 사용하는게 좋은 이유

이유를 설명하기에 앞서, DTO를 사용하지 않는 경우와 사용하는 경우를 비교해보자.

DTO 사용 X

  • Service 계층에서는 Member의 name 정보가 필요한 상황이다.
  • 이제 Service 계층에서는 MemberRepository에 Member의 name을 요구하게 된다.
  • MemberRepository는 DB에서 Member의 모든 정보를 담아 Member 객체를 Service 계층에 반환하게 된다.
  • Service 계층에서는 반환 받은 Member 객체에서 name 필드를 get함으로써 name 정보를 얻는다.

DTO 사용 O

  • 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 계층의 메서드가 '강한' 의존성을 없앤다.

Service 계층의 메서드는 Repository 계층의 메서드의 반환 타입(Member)에 그대로 의존하고 있다. 다시 말해, 자신이 원하는 방식으로 반환 받고 있지 못 하다. 이렇게 다른 객체에 강한 의존성을 가지고 있다면, 올바른 객체간의 협력 관계라고 보기 힘들다. 추후에 변화에 대응하기도 힘들어 보인다.


사실 위의 두 가지 이유를 관통하는 핵심은 객체지향이라는 생각이 든다.

객체는 캡슐화를 통해 자신의 필드로의 접근을 제한해야 하고, 올바른 메시지를 주고 받음으로써 협력 관계를 구축해야 한다.

개발 상황에서의 DTO

DTO에 대한 고민은 API 서버를 개발하며 줄곧 있었다. 객체지향에 대해 제대로 신경쓰기 전에는 getter와 setter를 남발하고, 객체 간 강한 의존성은 신경쓰지 않았다.

가장 고민을 이끌었던 상황은
Controller - Service 사이의 DTO이다.

Controller에서 RequestBody를 통해 클라이언트로부터 DTO를 입력받았다고 하자.

이 DTO를 토대로 Service 계층에서 로직을 수행하는 방법은

1) DTO를 그대로 넘겨준다.
2) 엔티티로 변환해 넘겨준다.
3) Service 계층을 위한 DTO로 변환해 넘겨준다.

세 가지로 생각이 든다. 결론부터 얘기하자면 3) 처럼 개발하는 것이 바람직하다고 생각한다.

각 상황에 대해 설명하자면,

DTO를 그래도 넘겨준다.

이는 위에서 설명했던 대로, Controller - Service 사이에 강한 의존성이 생기게 되고, Service는 자신이 원하는 방식으로 정보를 제공 받지 못하게 된다.

엔티티로 변환해 넘겨준다.

Controlle가 클라이언트로부터 받은 DTO가 Member의 회원가입 DTO라고 한다면, 이를 토대로
Controller 계층에서 new Member(params);를 통해 Member 엔티티를 생성해 Service 계층에 넘겨줄 수 있다.

이 방법은 나쁘지 않다고 생각이 든다. 하지만, 완벽하지 않은(비영속인 상태) 엔티티가 파라미터로 넘겨지는 것이 매우 어색하다. 어쩌면 이 비영속 상태의 엔티티가 3)처럼 Service 계층이 원하는 방식의 DTO라고 생각이 들 수도 있지만, Controller 계층에서 도메인과 관련된 로직을 수행하는 것은 Controller의 역할을 넘어선다고 생각한다.

Service 계층을 위한 DTO로 변환해 넘겨준다.

이 그림이 가장 핵심을 설명하기에 첨부했다.

위처럼 각 계층마다 자신이 원하는 파라미터 / 반환 타입을 가지고 계층간, 객체간 메시지를 주고 받으며 협력하는 것이 바람직하다고 생각한다.

예시

Controller

public void postMember(@RequestBody PostMemberReq postMemberReq) {
	// PostMemberReq (Controller) -> MemberJoinParams (Service)
	MemberJoinParams memberJoinParams = new MemberJoinParams(
    	postMemberReq.getName();
        postMemberReq.getEmail();
    );
    
	memberService.join(memberJoinParams);
}

DTO 객체 Naming

DTO 객체의 네이밍은 아직 어려운 것 같다...

나만의 규칙은

  • Controller : 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/

profile
울릉도에 별장 짓고 싶다

0개의 댓글