DTO 관리

Chooooo·2024년 7월 26일
0

TIL

목록 보기
26/28

DTO 관리 : 효율성과 가독성의 균형

RESTFul API 개발에서 DTO(Data Transfer Object)는 클라이언트와 서버 간의 데이터 교환을 위한 핵심 요소.
API가 클라이언트가 보낸 Request를 받을 때 RequestDTO를 사용하고, 결과 값을 내려줄 때 ResponseDTO를 사용한다.
-> 문제는 API스펙을 고도화할수록 DTO가 너무 많아진다는 것.

또한 요청에 따라서 Request Body가 다르고, Response Body도 다르다.
-> 그래서 API 스펙을 많이 뽑을수록 DTO 패키지가 지저분해진다.

Domain 분리 : Domain 별 DTO 관리

프로젝트가 어느정도 크기가 커지면 로직 안에서 동작하는 클래스들은 domain별로 관리하는게 좋다. (ex. 사용자, 주문, 상품 등) 별로 DTO를 그룹화
-> domain 별로 class 정리

장점 :

  • 관련 DTO들을 논리적으로 그룹화하여 코드 탐색 용이성 증가
  • 도메인 중심의 설계를 통한 비즈니스 로직 이해도 향상

구현 예시

com.example.dto
├── user
│   ├── UserRequestDTO
│   └── UserResponseDTO
├── order
│   ├── OrderRequestDTO
│   └── OrderResponseDTO
└── product
    ├── ProductRequestDTO
    └── ProductResponseDTO

Inner static class를 활용한 DTO 구조화

여기에 더해서 더 간결해질 방법이 있다.
방법 : 하나의 클래스 안에 Request와 Response DTO를 내부 정적 클래스로 정의한다.
장점 :

  • 관련 DTO들을 하나의 파일에서 관리하여 코드 응집도 향상
  • 패키지 구조 단순화
  • 연관된 DTO들을 쉽게 찾을 수 있어 개발 효율성 증가
public class UserDTO {
    public static class Request {
        // Request 관련 필드 및 메서드
    }
    
    public static class Response {
        // Response 관련 필드 및 메서드
    }
}

Domain 분리 + inner static class 결합

  • 도메인 분리와 Inner static Class를 결합하여 사용할 수 있다.
com.example.dto
├── user
│   └── UserDTO.java (내부에 Request, Response 등의 Static Class 포함)
├── order
│   └── OrderDTO.java (내부에 Request, Response 등의 Static Class 포함)
└── product
    └── ProductDTO.java (내부에 Request, Response 등의 Static Class 포함)

이러한 방식의 장점 :

  • 도메인별로 DTO를 분리하여 전체적인 구조 명확히 한다.
  • 각 도메인 내에서 관련 DTO들을 Inner Static Class로 그룹화하여 세부적인 관리가 용이하다.
  • 코드의 가독성과 유지보수성이 향상된다.
  • API 스펙 변경 시 영향 범위를 최소화할 수 있음

DTO 작성 예시

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import jakarta.validation.constraints.*;
import java.time.LocalDateTime;

public class UserDTO {

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class Request {
        @NotBlank(message = "이름은 필수입니다.")
        @Size(max = 50, message = "이름은 50자를 초과할 수 없습니다.")
        private String name;

        @NotBlank(message = "이메일은 필수입니다.")
        @Email(message = "유효한 이메일 주소를 입력해주세요.")
        private String email;

        @NotBlank(message = "비밀번호는 필수입니다.")
        @Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하여야 합니다.")
        @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[@#$%^&+=]).*$", 
                 message = "비밀번호는 숫자, 문자, 특수문자를 포함해야 합니다.")
        private String password;

        @NotNull(message = "생년월일은 필수입니다.")
        @Past(message = "생년월일은 과거 날짜여야 합니다.")
        private LocalDateTime birthDate;
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class Response {
        private Long id;
        private String name;
        private String email;
        private LocalDateTime birthDate;
        private LocalDateTime createdAt;
        private LocalDateTime updatedAt;

        @JsonIgnore
        private String password;

        public static Response from(User user) {
            return Response.builder()
                    .id(user.getId())
                    .name(user.getName())
                    .email(user.getEmail())
                    .birthDate(user.getBirthDate())
                    .createdAt(user.getCreatedAt())
                    .updatedAt(user.getUpdatedAt())
                    .build();
        }
    }
}

ref. 블로그

profile
back-end, 지속 성장 가능한 개발자를 향하여

0개의 댓글