Backend/Spring

[λ²ˆμ—­] Data Transfer Object (DTO) in Spring Boot

Seyun(Marco) 2024. 1. 25. 23:34
728x90

πŸ’‘ 원본글 : https://towardsdev.com/data-transfer-object-dto-in-spring-boot-c00678cc5946

 

Data Transfer Object (DTO) in Spring Boot

Explore the benefits of using Data Transfer Objects (DTOs) in Spring Boot, with examples including manual DTO creation, ModelMapper, and…

towardsdev.com

이 κΈ€μ—μ„œλŠ” Spring Bootμ—μ„œ 데이터 전솑 객체(DTO)의 이점을 νƒκ΅¬ν•˜λ©°, μˆ˜λ™ DTO 생성, ModelMapper, Lombok을 ν¬ν•¨ν•œ μ˜ˆμ œλ“€μ„ μ‚΄νŽ΄λ³Ό κ²ƒμž…λ‹ˆλ‹€.

Photo by Joshua Sortino on Unsplash

1. 데이터 전솑 객체(DTO)λž€ 무엇인가?

데이터 전솑 객체(DTO)λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ λ‹€λ₯Έ 계측 κ°„ 데이터λ₯Ό μΊ‘μŠν™”ν•˜κ³  μ „μ†‘ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” λ””μžμΈ νŒ¨ν„΄μž…λ‹ˆλ‹€. DTOλŠ” 일반적으둜 ν•„μš”ν•œ ν•„λ“œλ§Œ ν¬ν•¨ν•˜λŠ” κ°€λ²Όμš΄ 객체둜, λΉ„μ§€λ‹ˆμŠ€ λ‘œμ§μ€ ν¬ν•¨ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ λ‹€λ₯Έ λΆ€λΆ„λ“€, 예λ₯Ό λ“€μ–΄ ν”„λ‘ νŠΈμ—”λ“œμ™€ λ°±μ—”λ“œ μ‚¬μ΄λ‚˜ λΆ„μ‚° μ‹œμŠ€ν…œ λ‚΄μ˜ λ‹€μ–‘ν•œ λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ κ°„μ˜ 데이터λ₯Ό μš΄λ°˜ν•˜λŠ” ꡬ쑰둜 μ‚¬μš©λ©λ‹ˆλ‹€.

특히 Spring Boot μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ DTOλŠ” 컨트둀러 계측, μ„œλΉ„μŠ€ 계측, 그리고 μ˜μ†μ„± 계측 κ°„ 데이터λ₯Ό 전솑해야 ν•  λ•Œ μœ μš©ν•©λ‹ˆλ‹€. DTOλ₯Ό μ‚¬μš©ν•¨μœΌλ‘œμ¨ λ‚΄λΆ€ 데이터 λͺ¨λΈκ³Ό μ™ΈλΆ€ ν‘œν˜„(presentation)을 뢄리할 수 있으며, μ΄λŠ” 데이터 전솑에 λŒ€ν•œ 더 λ‚˜μ€ ν†΅μ œλ₯Ό κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.

2. Spring Boot λ‚΄μ—μ„œ DTOλ₯Ό μ‚¬μš©μ˜ 이점

Spring Boot μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ DTOλ₯Ό μ‚¬μš©ν•˜λ©΄ μ—¬λŸ¬ 가지 이점이 μžˆμŠ΅λ‹ˆλ‹€.

  1. 데이터 격리: DTOλ₯Ό μ‚¬μš©ν•˜λ©΄ μ™ΈλΆ€ 세계에 λ…ΈμΆœλ˜λŠ” 데이터λ₯Ό λ‚΄λΆ€ 도메인 λͺ¨λΈλ‘œλΆ€ν„° 뢄리할 수 μžˆμŠ΅λ‹ˆλ‹€. λ―Όκ°ν•˜κ±°λ‚˜ λΆˆν•„μš”ν•œ λ°μ΄ν„°μ˜ λ…ΈμΆœμ„ λ°©μ§€ν•˜κ³ , 데이터 κ΅ν™˜μ„ μœ„ν•œ λͺ…ν™•ν•œ κ·œμΉ™μ„ μ œκ³΅ν•©λ‹ˆλ‹€.
  2. μ˜€λ²„ν—€λ“œ κ°μ†Œ: DTOλŠ” νŠΉμ •ν•œ 유슀 μΌ€μ΄μŠ€μ— ν•„μš”ν•œ ν•„λ“œλ§Œ 포함할 수 μžˆμ–΄, λ„€νŠΈμ›Œν¬λ₯Ό 톡해 μ „μ†‘λ˜λŠ” 데이터 양을 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 큰 객체λ₯Ό 전솑 ν• λ•Œ λ°œμƒν•˜λŠ” 뢀담을 μ΅œμ†Œν™” ν•©λ‹ˆλ‹€.
  3. 버전 관리 및 ν˜Έν™˜μ„±: DTOλŠ” 버전 관리λ₯Ό μš©μ΄ν•˜κ²Œ ν•˜κ³ , μ—­ν˜Έν™˜μ„±μ„ 보μž₯ ν•˜λŠ”λ° 도움을 μ€λ‹ˆλ‹€. 도메인 λͺ¨λΈκ³Ό λ³„κ°œλ‘œ DTOλ₯Ό λ°œμ „μ‹œν‚¬μˆ˜ μžˆμ–΄ API 변경을 더 μ‰½κ²Œ μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  4. λ³΄μ•ˆ ν–₯상: DTOλ₯Ό 톡해 λ…ΈμΆœλ˜λŠ” 데이터λ₯Ό μ œμ–΄ν•¨μœΌλ‘œμ¨ 데이터 μ˜€μΈŒμ„ λ°©μ§€ν•˜κ³  λ―Όκ°ν•œ 정보에 λŒ€ν•œ 접근을 μ œν•œν•˜μ—¬ λ³΄μ•ˆμ„ κ°•ν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  5. ν…ŒμŠ€νŠΈ ν–₯상: DTOλŠ” λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό λ‹¨μˆœν™” ν•©λ‹ˆλ‹€. λ³΅μž‘ν•œ 도메인 객체에 μ˜μ‘΄ν•˜μ§€ μ•Šκ³  ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€μ—μ„œ μ‰½κ²Œ μƒμ„±ν•˜κ³  μ‘°μž‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

3. Spring Bootμ—μ„œ DTOμ‚¬μš©ν•˜λŠ” λ‹€μ–‘ν•œ 방법

3.1. μˆ˜λ™μœΌλ‘œ DTOλ₯Ό 생성

이 μ ‘κ·Ό λ°©μ‹μ—μ„œλŠ” 도메인 μ—”ν‹°ν‹°μ˜ ꡬ쑰λ₯Ό λ°˜μ˜ν•΄ DTO 클래슀λ₯Ό μˆ˜λ™μœΌλ‘œ μƒμ„±ν•©λ‹ˆλ‹€. 그런 λ‹€μŒ 도메인 객체와 DTO 사이에 데이터λ₯Ό λ§€ν•‘ν•˜κΈ° μœ„ν•œ μ½”λ“œλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.

public class UserDTO {
    private Long id;
    private String username;
    private String email;

// Constructors, getters, and setters

}

μˆ˜λ™μœΌλ‘œ λ§€ν•‘ν•˜λŠ” 과정이 μžˆλŠ” controller λ©”μ„œλ“œλ₯Ό λ§Œλ“€μ–΄ μ£Όλ©΄ λ©λ‹ˆλ‹€.

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);

        UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId());
        userDTO.setUsername(user.getUsername());
        userDTO.setEmail(user.getEmail());

        return ResponseEntity.ok(userDTO);
    }
}

κ²°κ³Ό**:** GET μš”μ²­μœΌλ‘œ /api/users/1을 μš”μ²­ν•˜λ©΄ μ‘λ‹΅μœΌλ‘œ μœ μ € 데이터λ₯Ό ν¬ν•¨ν•œ ****JSON을 λ°›μ„μˆ˜ μžˆμŠ΅λ‹ˆλ‹€.

{
    "id": 1,
    "username": "john_doe",
    "email": "john.doe@example.com"
}

3.2. ModelMapper μ‚¬μš©

ModelMapperλ₯Ό Spring Boot ν”„λ‘œμ νŠΈμ—μ„œ 도메인 객체와 DTO κ°„μ˜ 맀핑을 μžλ™ν™” ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•˜λ €λ©΄, pom.xmlμ—μ„œ ν•΄λ‹Ή μ˜μ‘΄μ„±μ„ μΆ”κ°€ν•©λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ ModeMappingλ₯Ό μ‚¬μš©ν•˜μ—¬ 도메인 객체와 DTO κ°„μ˜ 맀핑을 쉽고 효율적으둜 μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>2.4.3</version>
</dependency>

Spring Boot μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ ModelMapperλ₯Ό Spring Bean으둜 μ‚¬μš©ν•˜κΈ° μœ„ν•΄, Configuration 클래슀 λ˜λŠ” Main Application ν΄λž˜μŠ€μ— Bean을 λ“±λ‘ν• μˆ˜ μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ „μ²΄μ—μ„œ ModelMapper의 ꡬ성(Configuration)을 ν• μˆ˜ μžˆμŠ΅λ‹ˆλ‹€.

Spring Boot μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ ModelMapper Bean을 μƒμ„±ν•˜λŠ” 방법은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyApplicationConfig {

    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }
}

μ—¬κΈ°μ„œ 도메인 객체λ₯Ό DTO둜 λ³€ν™˜ν•˜κΈ° μœ„ν•΄ ModelMapperλ₯Ό μ•„λž˜μ™€ 같이 μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;

    @Autowired
    private ModelMapper modelMapper; // Autowire the ModelMapper bean

    @GetMapping("/{id}")
    public ResponseEntity getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);

        UserDTO userDTO = modelMapper.map(user, UserDTO.class); // Use ModelMapper for mapping

        return ResponseEntity.ok(userDTO);
    }
}

κ²°κ³Ό: μˆ˜λ™ DTO 생성 μ˜ˆμ œμ™€ λ™μΌν•œ κ²°κ³Όκ°€ λ‚˜μ˜΅λ‹ˆλ‹€.

3.3. Lombok μ‚¬μš©

Lombok μ˜μ‘΄μ„±μ„ pom.xml νŒŒμΌμ— μΆ”κ°€ν•˜μ„Έμš”.

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
    <scope>provided</scope>
</dependency>

User 엔티티에 DTOλ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄ μ•„λž˜μ™€ 같이 μž‘μ„±ν•˜μ„Έμš”.

import lombok.Data;

@Data
public class UserDTO {
    private Long id;
    private String username;
    private String email;
}

이제 도메인 객체λ₯Ό DTO둜 λ³€ν™˜ν—ˆκΈ° μœ„ν•΄ μ•„λž˜μ™€ 같이 Lombok을 μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);

        UserDTO userDTO = UserDTO.builder()
                .id(user.getId())
                .username(user.getUsername())
                .email(user.getEmail())
                .build();

        return ResponseEntity.ok(userDTO);
    }
}

κ²°κ³Ό: μˆ˜λ™ DTO 생성 μ˜ˆμ œμ™€ λ™μΌν•œ κ²°κ³Όκ°€ λ‚˜μ˜΅λ‹ˆλ‹€.

4. DTOμ—μ„œ λ‹€μ–‘ν•œ μœ ν˜•μ˜ κ°’ ν¬λ§·νŒ… ν•˜κΈ°

DTOμ—μ„œ λ‹€μ–‘ν•œ μœ ν˜•μ˜ 값을 ν¬λ§·νŒ…ν•˜λŠ” 것은 데이터가 μ§λ ¬ν™”λ˜κ±°λ‚˜ ν‘œμ‹œλ  λ•Œ νŠΉμ • ν˜•μ‹μœΌλ‘œ μ œκ³΅λ˜λ„λ‘ ν•˜λŠ” 데 ν•„μš”ν•œ 일반적인 μš”κ΅¬μ‚¬ν•­μž…λ‹ˆλ‹€. ν¬λ§·νŒ…ν•˜λ €λŠ” κ°’μ˜ μœ ν˜•μ— 따라, μ–΄λ…Έν…Œμ΄μ…˜, μ‚¬μš©μž μ •μ˜ λ©”μ„œλ“œ λ˜λŠ” μ™ΈλΆ€ 라이브러리λ₯Ό ν¬ν•¨ν•œ μ—¬λŸ¬ μ ‘κ·Ό 방식을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜μ—μ„œλŠ” DTOμ—μ„œ λ‹€μ–‘ν•œ μœ ν˜•μ˜ 값을 ν¬λ§·νŒ…ν•˜λŠ” 방법에 λŒ€ν•΄ μ„€λͺ…ν•˜κ² μŠ΅λ‹ˆλ‹€.

4.1. Date와 Time 포맷

4.1.1. @JsonFormat μ–΄λ…Έν…Œμ΄μ…˜ μ‚¬μš©(Jackson)

DTOμ—μ„œ λ‚ μ§œμ™€ μ‹œκ°„ 값을 ν¬λ§·νŒ… ν•˜κΈ° μœ„ν•΄, JSON 직렬화에 ν”νžˆ μ‚¬μš©λ˜λŠ” Jackson λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ μ œκ³΅ν•˜λŠ” @JsonFormat μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

import com.fasterxml.jackson.annotation.JsonFormat;

public class UserDTO {
    private Long id;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
    private Date registrationDate;

    // Other fields, getters, and setters
}

이 μ˜ˆμ œμ—μ„  registrationDate ν•„λ“œλ₯Ό @JsonFormat을 μ‚¬μš©ν•΄μ„œ λ‚ μ§œμ™€ μ‹œκ°„μ— λŒ€ν•΄μ„œ νŠΉλ³„ν•˜κ²Œ λ³€ν™˜ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

4.1.2. SimpleDateFormat μ‚¬μš©(Custom Method)

DTO 클래슀 내에 μ‚¬μš©μž μ •μ˜ gatter λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•¨μœΌλ‘œμ¨ λ‚ μ§œμ™€ μ‹œκ°„μ„ ν¬λ§·νŒ…ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. 이 λ©”μ†Œλ“œλŠ” 포맷된 λ‚ μ§œλ₯Ό λ¬Έμžμ—΄λ‘œ λ°˜ν™˜ν•©λ‹ˆλ‹€.

import java.text.SimpleDateFormat;

public class UserDTO {
    private Long id;
    private Date registrationDate;

    public String getFormattedRegistrationDate() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(registrationDate);
    }

    // Other fields, getters, and setters
}

4.2. 숫자 ν¬λ§·νŒ…

4.2.1. @NumberFormat μ–΄λ…Έν…Œμ΄μ…˜ μ‚¬μš© (Spring)

μˆ«μžλ‚˜ 톡화와 같은 숫자 값을 ν¬λ§·νŒ…ν•˜κΈ° μœ„ν•΄ Springμ—μ„œ μ œκ³΅ν•˜λŠ” @NumberFormat μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ 숫자 ν¬λ§·νŒ… νŒ¨ν„΄μ„ 지정할 수 μžˆμŠ΅λ‹ˆλ‹€.

import org.springframework.format.annotation.NumberFormat;

public class ProductDTO {
    private Long id;

    @NumberFormat(pattern = "#,###.00")
    private BigDecimal price;

    // Other fields, getters, and setters
}

이 μ˜ˆμ œμ—μ„œλŠ” price ν•„λ“œμ— @NumberFormat μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•΄μ„œ 숫자λ₯Ό ν¬λ§·νŒ…ν•˜λŠ” νŒ¨ν„΄μ„ μ§€μ •ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

4.2.2. DecimalFormat μ‚¬μš© (Custom Method)

DTO 클래슀 내에 μ‚¬μš©μž μ •μ˜ getter λ©”μ†Œλ“œλ₯Ό μ œκ³΅ν•˜μ—¬ 숫자λ₯Ό ν¬λ§·νŒ…ν• μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. 이 λ©”μ†Œλ“œλŠ” DecimalFormat을 μ‚¬μš©ν•˜μ—¬ 포맷된 숫자λ₯Ό λ¬Έμžμ—΄λ‘œ λ°˜ν™˜ν•©λ‹ˆλ‹€.

import java.text.DecimalFormat;

public class ProductDTO {
    private Long id;
    private BigDecimal price;

    public String getFormattedPrice() {
        DecimalFormat df = new DecimalFormat("#,###.00");
        return df.format(price);
    }

    // Other fields, getters, and setters
}

4.3. String ν¬λ§·νŒ…

4.3.1. Custom Methods μ‚¬μš©

λ¬Έμžμ—΄ 값을 ν¬λ§·νŒ…ν•˜κΈ° μœ„ν•΄, ν•„μš”μ— 따라 λ¬Έμžμ—΄μ„ μ‘°μž‘ν•˜λŠ” μ‚¬μš©μž μ •μ˜ getter λ©”μ„œλ“œλ₯Ό DTO 클래슀 내에 생성할 수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, 곡백을 자λ₯΄κ±°λ‚˜, 단어λ₯Ό λŒ€λ¬Έμžλ‘œ λ§Œλ“€κ±°λ‚˜, 기타 λ‹€λ₯Έ λ¬Έμžμ—΄ μ‘°μž‘ λ‘œμ§μ„ μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

public class ArticleDTO {
    private Long id;
    private String title;

    public String getFormattedTitle() {
        // Custom formatting logic here
        return title.trim(); // Example: Trim whitespace
    }

    // Other fields, getters, and setters
}

4.4. Enum ν¬λ§·νŒ…

4.4.1. Custom Method μ‚¬μš©

DTOμ—μ„œ μ—΄κ±°ν˜•(enum)을 λ‹€λ£° λ•Œ, μ—΄κ±°ν˜• κ°’μ˜ 포맷된 ν‘œν˜„μ„ λ°˜ν™˜ν•˜λŠ” μ‚¬μš©μž μ •μ˜ getter λ©”μ„œλ“œλ₯Ό 생성할 수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, μ—΄κ±°ν˜• 값을 λŒ€λ¬Έμžλ‘œ λ³€ν™˜ν•˜κ±°λ‚˜ λ‹€λ₯Έ ν‘œν˜„μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

public class OrderDTO {
    private Long id;
    private OrderStatus status;

    public String getFormattedStatus() {
        return status.toString().toUpperCase(); // Example: Convert to uppercase
    }

    // Other fields, getters, and setters
}

4.5. Boolean ν¬λ§·νŒ…

4.5.1. Custom Methods μ‚¬μš©

Boolean 값에 λŒ€ν•΄μ„œλŠ” “true” λ˜λŠ” “falseλŒ€μ‹ μ— “Yes”, “No”와 같은 포맷된 ν‘œν˜„μ„ μ‚¬μš©μž μ •μ˜ getter λ©”μ†Œλ“œλ‘œ 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.

public class UserDTO {
    private Long id;
    private boolean isActive;

    public String getFormattedIsActive() {
        return isActive ? "Yes" : "No"; // Example: Convert to "Yes" or "No"
    }

    // Other fields, getters, and setters
}

μ΄λŸ¬ν•œ μ ‘κ·Ό 방법듀을 μ‚¬μš©ν•¨μœΌλ‘œμ¨, νŠΉμ • μš”κ΅¬μ‚¬ν•­μ— 따라 DTO λ‚΄μ—μ„œ λ‹€μ–‘ν•œ μœ ν˜•μ˜ 값을 ν¬λ§·νŒ…ν•  수 있으며, DTOκ°€ μ§λ ¬ν™”λ˜κ±°λ‚˜ ν‘œμ‹œλ  λ•Œ 데이터가 μ›ν•˜λŠ” ν˜•μ‹μœΌλ‘œ μ œκ³΅λ˜λ„λ‘ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

5. μΆ”κ°€μ μœΌλ‘œ κ³ λ €ν•΄μ•Ό ν•  점과 Best Practice

5.1. DTOμ—μ„œμ˜ μœ νš¨μ„± 검사

DTOλ₯Ό λ‹€λ£° λ•Œ 데이터 검증을 κ³ λ €ν•˜λŠ” 것이 ν•„μˆ˜μ μž…λ‹ˆλ‹€. DTO에 λ“€μ–΄μ˜€λŠ” 데이터가 ν•„μš”ν•œ μ œμ•½ 쑰건과 λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™μ„ μΆ©μ‘±ν•˜λŠ”μ§€ ν™•μΈν•˜κΈ° μœ„ν•΄ 데이터λ₯Ό 검증해야 ν•©λ‹ˆλ‹€. Spring의 @NotNull, @Size λ˜λŠ” μ‚¬μš©μž μ •μ˜ 검증 μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ—¬ DTO ν•„λ“œλ₯Ό 검증할 수 μžˆμŠ΅λ‹ˆλ‹€.

Spring의 @NotBlank μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•œ DTO κ²€μ¦μ˜ μ˜ˆλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

public class UserDTO {
    @NotNull
    private Long id;

    @NotBlank
    @Size(min = 5, max = 50)
    private String username;

    @Email
    private String email;

    // Constructors, getters, and setters
}

5.2. λ³΅μž‘ν•œ 쀑첩 객체 DTO

μ‹€μ œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œλŠ” μ€‘μ²©λœ κ°μ²΄λ‚˜ 관계λ₯Ό λ‹€λ£° λ•Œ DTOκ°€ 더 λ³΅μž‘ν•΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ ꡬ쑰λ₯Ό μ •ν™•ν•˜κ²Œ λ‚˜νƒ€λ‚΄κΈ° μœ„ν•΄ μ€‘μ²©λœ DTOλ₯Ό 생성할 ν•„μš”κ°€ μžˆμŠ΅λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄, Userκ°€ μ—¬λŸ¬ Address 객체와 μ—°κ΄€λ˜μ–΄ μžˆλ‹€λ©΄, μ€‘μ²©λœ AddressDTOλ₯Ό ν¬ν•¨ν•˜λŠ” UserDTOλ₯Ό 생성할 수 μžˆμŠ΅λ‹ˆλ‹€:

public class UserDTO {
    private Long id;
    private String username;
    private String email;
    private List addresses;

    // Constructors, getters, and setters
}

5.3. DTO 버전 관리

μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ λ°œμ „ν•¨μ— 따라 DTO에 변경을 ν•΄μ•Ό ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. μ—­ν˜Έν™˜μ„±μ„ μœ μ§€ν•˜κΈ° μœ„ν•΄ DTO의 버전 관리λ₯Ό κ³ λ €ν•΄λ³΄μ„Έμš”. μ΄λŠ” DTO ν΄λž˜μŠ€μ— 버전 μ‹λ³„μžλ₯Ό μΆ”κ°€ν•˜κ±°λ‚˜ ν•„μš”ν•  λ•Œ μƒˆλ‘œμš΄ DTO 버전을 μƒμ„±ν•¨μœΌλ‘œμ¨ 달성할 수 μžˆμŠ΅λ‹ˆλ‹€.

5.4. RESTful APIμ—μ„œμ˜ DTO

DTOλŠ” ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„ 간에 κ΅ν™˜λ˜λŠ” 데이터λ₯Ό λ‚˜νƒ€λ‚΄κΈ° μœ„ν•΄ RESTful APIμ—μ„œ 일반적으둜 μ‚¬μš©λ©λ‹ˆλ‹€. RESTful μ—”λ“œν¬μΈνŠΈλ₯Ό 섀계할 λ•Œ, νŠΉμ • μ‚¬μš© 사둀와 ν΄λΌμ΄μ–ΈνŠΈ μš”κ΅¬μ‚¬ν•­μ— 맞게 DTOλ₯Ό μ‹ μ€‘ν•˜κ²Œ μ„ νƒν•˜κ³  ꡬ쑰화해야 ν•©λ‹ˆλ‹€. μ΄λŠ” 효율적인 데이터 전솑과 λͺ…ν™•ν•œ API 계약을 보μž₯ν•©λ‹ˆλ‹€.

6. Spring Validationκ³Ό DTO μ‚¬μš©

Spring은 컨트둀러 λ©”μ†Œλ“œμ—μ„œ @Valid μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ—¬ DTOλ₯Ό κ²€μ¦ν•˜λŠ” κ°•λ ₯ν•œ λ©”μ»€λ‹ˆμ¦˜μ„ μ œκ³΅ν•©λ‹ˆλ‹€. DTO νŒŒλΌλ―Έν„°μ— **@Valid**λ₯Ό μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ 달면, Spring은 DTO ν΄λž˜μŠ€μ— μ •μ˜λœ 검증 μ œμ•½ 쑰건을 기반으둜 μžλ™μœΌλ‘œ 검증을 νŠΈλ¦¬κ±°ν•©λ‹ˆλ‹€.

컨트둀러 λ©”μ†Œλ“œμ—μ„œ DTO 검증을 μ‚¬μš©ν•˜λŠ” μ˜ˆμ‹œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("/create")
    public ResponseEntity createUser(@Valid @RequestBody UserDTO userDTO) {
        // Your validation logic is automatically triggered

        // Map UserDTO to User entity and save it
        User user = modelMapper.map(userDTO, User.class);
        User savedUser = userService.saveUser(user);

        // Return the saved UserDTO
        UserDTO savedUserDTO = modelMapper.map(savedUser, UserDTO.class);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedUserDTO);
    }
}

이 μ˜ˆμ œμ—μ„œλŠ” @Valid μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ—¬ UserDTO ν΄λž˜μŠ€μ—μ„œ μ •μ˜ν•œ μ œμ•½ 쑰건을 기반으둜 검증을 νŠΈλ¦¬κ±°ν•©λ‹ˆλ‹€. 검증이 μ‹€νŒ¨ν•˜λ©΄ Spring은 μžλ™μœΌλ‘œ 검증에 λŒ€ν•œ 였λ₯˜λ₯Ό μ²˜λ¦¬ν•˜κ³  μ μ ˆν•œ 였λ₯˜ λ©”μ‹œμ§€λ₯Ό ν¬ν•¨ν•œ 응닡을 λ°˜ν™˜ν•©λ‹ˆλ‹€.

7. λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ μ•„ν‚€ν…μ²˜μ—μ„œμ˜ DTO

λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ μ•„ν‚€ν…μ²˜μ—μ„œλŠ” DTOκ°€ λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ κ°„μ˜ 경계λ₯Ό μ •μ˜ν•˜λŠ” μ€‘μš”ν•œ 역할을 ν•©λ‹ˆλ‹€. 각 λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€λŠ” 자체적으둜 νŠΉμ • μš”κ΅¬ 사항에 λ§žλŠ” DTO μ„ΈνŠΈλ₯Ό κ°€μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€. 이 λΆ„λ¦¬λŠ” λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ κ°„μ˜ λŠμŠ¨ν•œ 결합을 보μž₯ν•˜κ³  λ…λ¦½μ μœΌλ‘œ 진화할 수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€.

DTOλŠ” λ˜ν•œ λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ 간에 μ „μ†‘λ˜λŠ” 데이터 양을 μ€„μ΄λŠ” 데 도움이 되며, μ΄λŠ” λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ 기반 μ‹œμŠ€ν…œμ˜ μ„±λŠ₯κ³Ό ν™•μž₯성을 μœ μ§€ν•˜λŠ” 데 μ€‘μš”ν•©λ‹ˆλ‹€.

8. κ²°λ‘ 

데이터 전솑 객체 (DTO)λŠ” Spring Boot μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ ν•„μˆ˜μ μ΄λ©°, μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ λ‹€λ₯Έ λ ˆμ΄μ–΄μ™€ μ™ΈλΆ€ μ‹œμŠ€ν…œ μ‚¬μ΄μ˜ 닀리 역할을 ν•©λ‹ˆλ‹€. DTOλ₯Ό μ‹ μ€‘ν•˜κ²Œ μ„€κ³„ν•˜κ³  μ‚¬μš©ν•¨μœΌλ‘œμ¨ 데이터 격리λ₯Ό κ°œμ„ ν•˜κ³  μ˜€λ²„ν—€λ“œλ₯Ό 쀄이며 λ³΄μ•ˆμ„ κ°•ν™”ν•˜κ³  ν…ŒμŠ€νŠΈλ₯Ό λ‹¨μˆœν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€. DTOλ₯Ό μˆ˜λ™μœΌλ‘œ μž‘μ„±ν•˜κ±°λ‚˜ ModelMapper와 같은 라이브러리λ₯Ό μ‚¬μš©ν•˜κ±°λ‚˜ μ½”λ“œ κ°„μ†Œν™”λ₯Ό μœ„ν•΄ Lombok을 ν™œμš©ν•˜λ“ , 핡심은 ν”„λ‘œμ νŠΈμ˜ μš”κ΅¬ 사항과 μœ μ§€ λ³΄μˆ˜μ„±μ— κ°€μž₯ μ ν•©ν•œ μ ‘κ·Ό 방식을 μ„ νƒν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. DTOκ°€ μ•„ν‚€ν…μ²˜μ˜ μ€‘μš”ν•œ λΆ€λΆ„μœΌλ‘œ μ‘΄μž¬ν•˜λ©΄, κ²¬κ³ ν•˜κ³  효율적인 Spring Boot μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ κ΅¬μΆ•ν•˜λŠ” 데 더 잘 κ°–μΆ”μ–΄μ§‘λ‹ˆλ‹€.

 

μΆ”κ°€μ μœΌλ‘œ μ½μ–΄λ³Όλ§Œν•œ κΈ€:

Rest API in Spring Boot

Multiple application properties in spring boot

728x90
728x90