[DTO] DTO의 사용범위에 대하여

hynnch2·2022년 3월 17일
1
post-custom-banner

이번 프로젝트를 진행하면서 DTO를 처음 사용했습니다.

DTO를 처음 도입하면서 의문이 들었던 DTO의 사용범위는 어디까지 정해야 할지,
Entity와 DTO의 변환 과정과 시점을 어떻게 나눌지 팀원과 얘기해보고 결정하면서 느낀 경험을 적어보고자 합니다.


DTO란?

먼저 DTO는 Data Transfer Object의 약자로 계층간 데이터 교환을 하기 위한 목적으로 사용됩니다.

DTO가 나오게 된 계기를 먼저 생각해보면,
Client와 직접 상호작용 하는 Controller에서 DB와 연관된 Entity를 직접 사용하지 않고, 데이터만 가져오기 위해 사용합니다.

DTO는 단지 Layer간의 데이터를 교환하기 위한 것이므로, 다른 로직을 포함할 필요가 없으며, 보통 Getter, Setter 함수만 구현되어 있습니다.

[출처: DAO, DTO, Entity Class 차이]


Validator와 DTO (응용)

spring에서는 DTO와 validator를 통해 client로부터 오는 데이터의 적합성 여부를 검사 할 수 있습니다.

먼저 gradle에 spring boot stater validation을 추가하고,
원하는 DTO를 작성한다음 controller에서 @valid를 통해 요청을 확인 할 수 있습니다.

ex) 실제 프로젝트 적용 내용.

public class CreateChallengeDTO {

	@NotBlank
	private String name;

	@NotNull
	private String description;

	@NotNull
	private Boolean isPrivate;

}
@PostMapping(value = "")
public ResponseEntity<ResponseChallengeDTO> createChallengeAPI(@Valid @RequestBody CreateChallengeDTO createChallengeDTO,
															   @AuthenticationPrincipal User user){
	Challenge createdChallenge = challengeService.createChallenge(createChallengeDTO, user);
	return ResponseEntity.status(HttpStatus.CREATED).body(ResponseChallengeDTO.convertDTO(createdChallenge));
}

@NotBlank, @NotNull, @NotEmpty 같은 조건을 이용해서 원하는 정보를 모두 포함하고 있는지, 규칙을 따르고 있는지 쉽게 확인 할 수 있습니다.

이러한 방식으로 spring에서는 Client와 Server간 DTO 송신을 통해 Entity의 정보를 은닉 할 수 있으며, DTO를 사용하여 Entity의 안정성을 확보 할 수 있습니다.


🤔 DTO의 적용 범위?

그럼 Client와 Server간 DTO를 활용하는 방법에 대해 알았지만, 의문이 드는 점이 있었습니다.

DTO는 Layer간 데이터를 교환하기 위해 사용된다고 하면, 다음과 같이 코드를 짜야 한다고 생각했습니다.

  • Controller - Service 에서 DTO를 사용해야 한다.
  • Repository에서는 Entity를 사용해야 한다.

그러므로 다음과 같은 방식으로 코드를 짰습니다.

  1. Controller에서 Service에 DTO를 그대로 넘긴다.
  2. Service에서 Entity로 변환 후 로직을 처리한다.
  3. 로직을 처리하고 반환할 Entity는 ResponseDTO로 변환한다.
  4. ResponseDTO를 Controller에게 전달하고 Client에 전송한다.

즉, Layer간 DTO를 전달하기 위해 Service에서 최종적으로 "Entity - DTO" 변환을 모두 전담하도록 코드를 작성하였습니다.


😢 문제 발생 1.

"다른 Service에서 현재 Service를 호출 하고 싶은데 ResponseDTO를 반환하니까 문제가 된다"

위의 방식대로 엄격하게 Service단에서 DTO를 보내기 위해서, Controller가 client에게 전달하는 정보만 DTO로 담아서 보내는 방식은 Controller - Service의 결합도를 증가시켰습니다.

즉, Service는 client에게 보낼 내용을 이미 알고 DTO로 변환했고, Controller가 아닌 곳에서는 Service 함수를 사용 할 수 없었습니다. (client에게 전달할 내용만 담았기 때문에)

  1. Controller 외에서 Service 이용 불가.
  2. Client에게 전달할 정보를 Service에서 이미 알고 전해야함.

이러한 문제점을 해결하기 위해 다음 방식은 다음과 같이 생각했습니다.

  1. Service에서 Entity를 반환하자.
    장점: Service의 자유도가 높아짐.
    단점: Controller에게 Entity를 전달함. DTO의 개념이 훼손됨.
  2. Model과 똑같은 DTO를 생성하고, 그것을 반환하도록 하자.
    장점: Controller외에 Entity의 정보를 쉽게 얻을 수 있음, Controller와의 격리성을 보존시킴, DTO의 개념을 잘 지킴.
    단점: 코드 늘어남.

팀원과 고민 끝에 2번으로 선택하기로 했고, 다음과 같이 수정했습니다.

  1. client에서 전달받은 createDTO, modifyDTO -> DTO로 변경.
  2. DTO를 Service에게 전달.
  3. Service에서 DTO -> Entity 변환, 로직수행 -> DTO 반환.
  4. Controller에서 responseDTO로 변환 후 client에게 전달.

엄격하게 DTO의 개념을 지키면서 Service의 자유도가 높아질 수 있었습니다.


🔥 문제 발생 2.

"Service에서 Client가 보낸 정보가 무엇인지 정확히 인지하고 있어야 한다"

이전 방식은 Server내에서는 DTO로 엄격하게 통신하기 위해서 Model과 같은 DTO로만 통신하기로 했습니다.

이 문제점은 client에서 보내는 DTO의 정보는 요청마다 필드의 여부가 다른데 (id, name, members ...), service에서는 DTO필드가 전부 존재하니 controller에서 온 DTO의 필드를 "실수없이"사용해야 했습니다.

오히려 없는 필드값이 없는 것에 get을 통해 nullPoint 오류를 낼 수 있고, 개발자의 실수가 발생 할 수 있는 부담요소가 되었습니다.

  1. Service가 Controller에서 온 필드 값을 전부 파악하고 있어야 함.
    (필드는 모두 가지고 있는 DTO를 주기 때문에)
  2. 계속 파일을 왔다 갔다 하면서 필드 값을 확인하고, 피로도가 높아지고 실수 가능성 높음.

결국 controller에서 주는 방식을 다르게 하기로 했습니다.

  1. Controller가 주는 값은 Client가 보낸 DTO를 그대로 보낸다.
  2. 필드 값을 꺼내서, 각자 하나씩 보낸다.

필드 값을 꺼내서 보내는 방식은, 비슷한 함수가 많아지면 매개변수 순서 등 코드 관리가 어려 울 것이라고 판단하여 1번의 방식을 선택했습니다.

그렇게 되면 Model과 같은 형태의 DTO를 지킬 필요가 없다고 생각했습니다. (이미 controller -> service가 model과 같은 DTO를 사용하지 않기 때문에)


🖐 우리의 결론

즉, 최종적으로

  • Model과 같은 DTO는 현재 꼭 필요한 것이 아니라고 판단 -> 삭제.
  • Entity를 전달하되, controller 및 다른 service에서 entity를 수정하는 코드는 작성 금지

결과적으로 어느정도 위험을 수용하고, ResponseDTO 클래스 안에 dtoToEntity 함수만 작성하여 controller에서 반환을 관리하기로 했습니다.

프로젝트 크기가 어느정도 커지고, 코드 관리가 어려워 진다면 entity의 수정도 예상하기 힘들어집니다.
그렇게 되면, 엄격하게 model과 같은 DTO를 반환하도록 수정 할 수 있겠지만, 현재는 이러한 방식으로 진행하기로 했습니다.


DTO의 개념만 공부했을 때는 "~~할 때 쓰는구나" 정도로만 인식했지만, 실제 코드를 작성하고 적용하면서 애매한 부분을 접하고 더 많이 알게 된 것 같습니다.

실제로 DTO와 Entity를 어디서 변환하는지는 각자 상황과 환경에 맞춰서 유동적으로 적용하는 부분도 있었고, 사람 by 사람이다 보니 예제도 많이 달랐습니다.

또, Model과 같은 DTO에 집착하다보니 문제점이 발생헀고, 이러한 부분은 어느정도 수용하면서 코드를 작성하는 법도 배운 것 같습니다.

다른 의견이 있거나 좋은 의견이 있으시면 댓글로 남겨주시면 감사하겠습니다!! 🤗

긴 글 읽어주셔서 감사합니다.


+ 추가 할 내용이나 부족한 부분이 있다면, 댓글 작성 부탁드립니다! :)

>>프로젝트 코드 보러가기.<<

profile
more than yesterday
post-custom-banner

2개의 댓글

comment-user-thumbnail
2022년 7월 16일

안녕하세요 🙂
좋은 글 잘 읽었습니다.
만약 service layer의 재사용성을 위해 controller로 entity 자체를 넘긴다면 lazy loading이 발생할 경우를 대비하여 트랜잭션이 controller layer까지 이어져야 할 것 같은데, 이에 대한 단점도 존재하지 않을까요?

1개의 답글