Spring Boot에서 깔끔하게 DTO 관리하기

박상훈·2020년 3월 4일
52

우리는 스토리보드를 보고 Api 명세를 작성할 때 테이블 설계와 동시에 Request와 Response 등 DTO 디자인을 고민하곤 한다. 하지만 기획 당시 계획한 디자인을 준수하여 개발을 하다보면 프로젝트 패키지와 소스들이 지저분해지는 경우가 있다.

왜?

  • Api별로 화면에 return하는 데이터가 달라 많은 DTO 파일을 생성하게된다.
  • Entity들의 데이터를 가공하여 DTO에 Set Method 혹은 builder로 매핑하는 코드가 길어진다. (이 문제 관련 내용은 다음 포스팅에서 다루도록 하겠다.)

이제부터 더 간결한 코드를 고민하는 시간을 가지며 위 문제점들을 개선한 내용을 정리하고자 한다.

너무 많고 많은 DTO(VO)들..

프로젝트 내 VO 혹은 DTO 패키지 안에 필요할 때마다 Class파일을 생성하면 위 사진과 같이 파일수가 매우매우 많아진다.

여기서 파생되는 문제점들은..

  • 일단 그냥 보기에 더럽다.(흐그는드)
  • 더 이상 ClassName이 중복되지 않는 DTO를 만들기가 어려워진다.
  • 필드들이 겹치는 DTO로 대충 Response를 내리다보니 Over-Fetching을 하게된다.

DTO를 Inner Class로

그렇다면 같은 기능을 수행하며 DTO 패키지 내 클래스 파일을 깔끔하게 관리할 수 있는 방법이 있을까?

필자가 주로 사용하는 방법은 Inner Class(Nested Class)로 DTO를 관리하는 것이다.

간단한 예시로 User정보를 저장하는 api와 조회하는 api를 만드는 상황을 가정해보자.
떠오르는 DTO를 나열하자면

  1. POST Api에서 Request Payload를 매핑할 DTO
  2. GET Api에서 Return해줄 Response DTO
  3. 레이어를 옮겨다니거나 결과를 Return하기 위해 실제 User정보를 담은 DTO

홀리몰리.. 우리는 벌써 2~3개의 DTO을 생성하여야한다.

하지만 User라는 도메인에 관련된 DTO들을 Class 하나에 묶어준다는 생각으로 User Class를 만든 후 Inner Class로 DTO들을 구현한다면..?

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

public class User {

    @Getter
    @AllArgsConstructor
    @Builder
    public static class Info {
        private int id;
        private String name;
        private int age;
    }

    @Getter
    @Setter
    public static class Request {
        private String name;
        private int age;
    }

    @Getter
    @AllArgsConstructor
    public static class Response {
        private Info info;
        private int returnCode;
        private String returnMessage;
    }
}

위와 같이 1개의 Class파일로 깔끔하게 관리할 수 있게 된다. 세밀한 Class 구조는 취향대로 바꿔서 써도 무방할 듯하다.

import com.parksh.demo.dto.DefaultResponse;
import com.parksh.demo.dto.user.User;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(“user”)
public class UserController {

    @GetMapping(/{user_id})
    public User.Response getUser(@PathVariable(“user_id”) String userId) {

        return new User.Response(new User.Info(), 200, “success”);
    }

    @PostMapping
    public DefaultResponse addUser(@RequestBody User.Info info) {

        return new DefaultResponse();
    }
}

(Controller 구현 부분)

이 방법을 채택하여 DTO를 관리한다면 조금 더 깔끔한 패키지를 만들 수 있고, DTO ClassName을 정하는게 수월해질 것이다.
(귀찮아서 Over-Fetching을 하지 말자!)

profile
AWS & BackEnd Engineering에 관심이 많은 개발자입니다.

10개의 댓글

comment-user-thumbnail
2020년 7월 31일

DTO 설계 관련해서 중복 코드등이 많아 지는것 같아서 고민이 많았는데! 보는 순간 이거다 싶었어요 감사합니다

답글 달기
comment-user-thumbnail
2020년 9월 13일

아주 좋은 방법이군요 호호!

답글 달기
comment-user-thumbnail
2020년 12월 30일

오 저도 이렇게 주로 코딩하는데!! 좋은내용 감사합니다!

답글 달기
comment-user-thumbnail
2021년 2월 1일

아리가또!!!!!!!!!!!!!!!!!!!!!!!!!!!

답글 달기
comment-user-thumbnail
2021년 3월 20일

정말 괜찮은 방법 같습니다!

답글 달기
comment-user-thumbnail
2021년 5월 11일

좋은 정보 감사합니다 ~!

답글 달기
comment-user-thumbnail
2021년 7월 27일

좋은 정보 감사합니다!

답글 달기
comment-user-thumbnail
2021년 8월 23일

오 DTO 설계 관련해서 좋은 인사이트네요 감사합니다

답글 달기
comment-user-thumbnail
2021년 10월 15일

안녕하세요 도움 많이 됐어요 ㅎㅎ 클래스마다 static을 사용하게 되면 메모리..? 에 대한 문제는 없나요?

답글 달기
comment-user-thumbnail
2021년 11월 4일

난 spring dto when request and response are same 이렇게 구글에 검색했는데 이 포스팅을 제일 위에 띄워줬고 너무 맘에 듭니다

답글 달기