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

AUSG·2020년 7월 7일
13


RESTFUL한 어플리케이션 환경에서 우리는 스토리보드를 보고 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을 하지 맙시다!)

글쓴이 👨‍💻

박상훈 - 👨‍👩‍👧‍👧AUSG (AWS University Student Group) 3기로 활동 중

관심사

  • Infra Engineeer, SRE Engineer, BackEnd Engineer
  • NodeJS, TypeScript, GraphQL, SpringBoot

Blog
https://velog.io/@p4rksh

Email
p4rksh@kakao.com

GitHub
p4rksh

profile
AWSKRUG University Student Group의 공식 벨로그 계정입니다. 멤버들이 돌아가며 글을 쓰고 있습니다.

5개의 댓글

comment-user-thumbnail
2021년 1월 13일

감사합니다람쥐!!!

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

감사합니다이소!!!

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

감사합니다이빙!!

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

감사합니다시마!!!

답글 달기
comment-user-thumbnail
2022년 1월 11일

저도 한번 따라해봐야겠어요이땅

답글 달기