[Spring MVC] DTO(Data Transfer Object)

Updated:

Categories:

Tags: , ,

๐Ÿ“Œ ๊ฐœ์ธ์ ์ธ ๊ณต๊ฐ„์œผ๋กœ ๊ณต๋ถ€๋ฅผ ๊ธฐ๋กํ•˜๊ณ  ๋ณต์Šตํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋ธ”๋กœ๊ทธ์ž…๋‹ˆ๋‹ค.
์ •ํ™•ํ•˜์ง€ ์•Š์€ ์ •๋ณด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฐธ๊ณ ๋ฐ”๋ž๋‹ˆ๋‹ค :๐Ÿ˜ธ
[ํ‹€๋ฆฐ ๋‚ด์šฉ์€ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ๋ณต๋ฐ›์œผ์‹ค๊ฑฐ์—์š”]

HTTP ์š”์ฒญ/์‘๋‹ต์—์„œ์˜ DTO

DTO ๋ž€?

  • Transfer๋ผ๋Š” ์˜๋ฏธ์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„์˜ ๊ฐ์ฒด
  • DTO๋Š” ์ฃผ๋กœ ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„ ์ชฝ์œผ๋กœ ์ „์†กํ•˜๋Š” ์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›์„ ๋•Œ, ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ ์ชฝ์œผ๋กœ ์ „์†กํ•˜๋Š” ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„๋กœ ์‚ฌ์šฉ
  • ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜์˜ ๊ธฐ๋ณธํ๋ฆ„

    Client

    โ€” โ†“โ†‘ โ€” DTO(โ†“Request/Responseโ†‘)

    โ€‹ Request : ํด๋ผ์ด์–ธํŠธ ์ชฝ์—์„œ JSON ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„ ์ชฝ์œผ๋กœ ์ „์†ก โ‡’ ์„œ๋ฒ„์—์„œ JSON ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ DTO๊ฐ™์€ JAVA๊ฐ์ฒด๋กœ ๋ณ€ํ™˜=์—ญ์ง๋ ฌํ™”(Deserialization)

    โ€‹ Response : ์„œ๋ฒ„์ชฝ์—์„œ client์—๊ฒŒ ์‘๋‹ต๋ฐ์ดํ„ฐ๋ฅผ ์ „์†ก , ์‘๋‹ต๋ฐ์ดํ„ฐ java์—์„œ JSONํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ =์ง๋ ฌํ™” (Serialization)

    API (Controller)

    โ€” โ†“โ†‘ โ€” Entity (DB์˜ ํ…Œ์ด๋ธ”๊ณผ 1:1๋กœ ๋งตํ•‘๋˜๋Š” ๊ฐ์ฒด)

    Service

    โ€” โ†“โ†‘ โ€” Entity

    Repository

    โ€” โ†“โ†‘ โ€”

    DB

DTO ์‚ฌ์šฉ์ด์œ 

  1. DTO ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•œ ์ฝ”๋“œ์˜ ๊ฐ„๊ฒฐ์„ฑ
    1. ํšŒ์›์ •๋ณด๋ฅผ ๋“ฑ๋กํ•  ๋•Œ ์ •๋ณด๊ฐ€ ๋งŽ์„ ์ˆ˜๋ก postMember()์— ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ถ”๊ฐ€๋˜๋Š” @RequestParam์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚จ โ‡’ DTO ํด๋ž˜์Šค๊ฐ€ ๋ฐ”๋กœ ์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋กœ ์ „๋‹ฌ๋ฐ›๋Š” ์—ญํ• ์„ ํ•ด์คŒ.

      [๊ธฐ์กด์ฝ”๋“œ]

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
       @RestController
       @RequestMapping("/v1/members")
       public class MemberController {
           @PostMapping
           public ResponseEntity postMember(@RequestParam("email") String email,
                                            @RequestParam("name") String name,
                                            @RequestParam("phone") String phone) {
               Map<String, String> map = new HashMap<>();
               map.put("email", email);
               map.put("name", name);
               map.put("phone", phone);
              
               return new ResponseEntity<Map>(map, HttpStatus.CREATED);
           }
      

      [DTO]

      1
      2
      3
      4
      5
      6
      7
      
       @RestController
       @RequestMapping("/v1/members")
       public class MemberController {
           @PostMapping
           public ResponseEntity postMember(MemberDto memberDto) {
               return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
           }
      
  2. ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์˜ ๋‹จ์ˆœํ™”
    1. ์„œ๋ฒ„ ์ชฝ์—์„œ ์œ ํšจํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๊ฒƒ์„ ์œ ํšจ์„ฑ(Validation) ๊ฒ€์ฆ
    2. HTTP ์š”์ฒญ์„ ์ „๋‹ฌ๋ฐ›๋Š” ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ๋Š” ์š”์ฒญ์„ ์ „๋‹ฌ๋ฐ›๋Š” ๊ฒƒ์ด ์ฃผ๋ชฉ์ ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ตœ๋Œ€ํ•œ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ž‘์„ฑํ•ด์•ผ ํ•จ.
    3. DTO ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋กœ์ง์„ DTO ํด๋ž˜์Šค๋กœ ๋นผ๋‚ด์–ด ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ์˜ ๊ฐ„๊ฒฐํ•จ์„ ์œ ์ง€๊ฐ€๋Šฅ

      [๊ธฐ์กด ์ฝ”๋“œ]

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
       @RestController
       @RequestMapping("/no-dto-validation/v1/members")
       public class MemberController {
           @PostMapping
           public ResponseEntity postMember(@RequestParam("email") String email,
                                            @RequestParam("name") String name,
                                            @RequestParam("phone") String phone) {
       				// (1) email ์œ ํšจ์„ฑ ๊ฒ€์ฆ
               if (!email.matches("^[a-zA-Z0-9_!#$%&'\\\\*+/=?{|}~^.-]+@[a-zA-Z0-9.-]+$")) {
                   throw new InvalidParameterException();
               }
               Map<String, String> map = new HashMap<>();
               map.put("email", email);
               map.put("name", name);
               map.put("phone", phone);
              
               return new ResponseEntity<Map>(map, HttpStatus.CREATED);
           }
       		...
       		...
       }
      

      [DTO]

      DTO ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋กœ์ง์„ DTO ํด๋ž˜์Šค๋กœ ๋นผ๋‚ด์–ด ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ์˜ ๊ฐ„๊ฒฐํ•จ์„ ์œ ์ง€๊ฐ€๋Šฅ โ‡’ Getter Setter๋กœ ๋ถˆ๋Ÿฌ์˜ค๊ฑฐ๋‚˜ , ํ•˜๊ธฐ ์ฝ”๋“œ์ฒ˜๋Ÿผ lombok ์‚ฌ์šฉ

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
       import lombok.Getter;
       import lombok.Setter;
              
       @Getter @Setter
       public class MemberDto {
           @Email
           private String email;
           private String name;
           private String phone;
           }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
       @RestController
       @RequestMapping("/v1/members")
       public class MemberController {
           @PostMapping
           public ResponseEntity postMember(@Valid MemberDto memberDto) {
               return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
           }
       		...
       		...
       }
              
      

    DTO ํด๋ž˜์Šค ์ƒ์„ฑ์‹œ ์ฃผ์˜์‚ฌํ•ญ

    • ๋ฉค๋ฒ„ ๋ณ€์ˆ˜ ์ด์™ธ์— getter ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์–ด์•ผ ํ•จ โ‡’ Response Body์— ํ•ด๋‹น ๋ฉค๋ฒ„ ๋ณ€์ˆ˜์˜ ๊ฐ’์ด ํฌํ•จ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒ
    • getter & setter ์ƒ์„ฑ ์‹œ alt+insert ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„ํŽธํ•˜๊ฒŒ ๋“ฑ๋ก ๊ฐ€๋Šฅ.

HTTP ์š”์ฒญ/์‘๋‹ต ๋ฐ์ดํ„ฐ์— DTO ์ ์šฉํ•˜๊ธฐ

โ‡’ ๊ธฐ์กด ์ฝ”๋“œ์—์„œ ๋ฆฌํŒฉํ† ๋งํ•˜๊ธฐ.

  1. HTTP Request Body๊ฐ€ JSON ํ˜•์‹
    1. @RequestBody ์• ๋„ˆํ…Œ์ด์…˜
      1. JSON ํ˜•์‹์˜ Request Body๋ฅผ MemberPostDto ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜์„ ์‹œ์ผœ์ฃผ๋Š” ์—ญํ•  (ํด๋ผ์ด์–ธํŠธ ์ชฝ์—์„œ ์ „์†กํ•˜๋Š” Request Body๋Š” JSON ํ˜•์‹์ด์–ด์•ผ ํ•œ๋‹ค)
    2. @ResponseBody ์• ๋„ˆํ…Œ์ด์…˜
      1. JSON ํ˜•์‹์˜ Response Body๋ฅผ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด DTO ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋ฅผ Response Body๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์—ญํ• 
      2. Spring MVC์—์„œ๋Š” ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ์— @ResponseBody ์• ๋„ˆํ…Œ์ด์…˜์ด ๋ถ™๊ฑฐ๋‚˜ ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ์˜ ๋ฆฌํ„ด ๊ฐ’์ด ResponseEntity์ผ ๊ฒฝ์šฐ, ๋‚ด๋ถ€์ ์œผ๋กœ HttpMessageConverter๊ฐ€ ๋™์ž‘ํ•˜๊ฒŒ ๋˜์–ด ์‘๋‹ต ๊ฐ์ฒด(์—ฌ๊ธฐ์„œ๋Š” DTO ํด๋ž˜์Šค์˜ ๊ฐ์ฒด)๋ฅผ JSON ํ˜•์‹์œผ๋กœ ๋ฐ”๊ฟ”์คŒ

DTO ์œ ํšจ์„ฑ ๊ฒ€์ฆ

Untitled

ํ•ด๋‹น ํšŒ์›๊ฐ€์ž… ๋ฐฑ์—”๋“œ์—์„œ ๊ณ ๋ คํ•ด๋ณผ ๋งŒํ•œ ์‚ฌํ•ญ.

  1. ์ด๋ฉ”์ผ ๊ฒ€์ฆ
  2. ์ด๋ฆ„ - ์ˆซ์žX, ํ•œ๊ตญ์ธ๋งŒ๊ฐ€๋Šฅ? -ํ•œ๊ธ€๋งŒ โ†’ annotation์—†์Œ
  3. ํœด๋Œ€์ „ํ™” 3๊ฐœ, 4๊ฐœ, 4๊ฐœ ์ˆซ์ž๋งŒ ์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ
  4. ๊ณต๋ฐฑ๊ฒ€์ฆํ•ด์•ผ๋จ. โ†’ annotation ์—†์Œ

DTO ํด๋ž˜์Šค์— ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์ ์šฉ

  1. ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•œ ์˜์กด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€

    1
    2
    3
    4
    5
    
     dependencies {
     	implementation 'org.springframework.boot:spring-boot-starter-validation'
     	...
     	...
     }
    
  2. ์œ ํšจ์„ฑ ๊ฒ€์ฆ์‹œ ์‚ฌ์šฉ๋˜๋Š” Annotation

    Annotation Description Null ํ—ˆ์šฉ = null์ผ ๊ฒฝ์šฐ ์œ ํšจ์„ฑ๊ฒ€์ฆ x,
    not null์ด๋ฉด ๊ฒ€์ฆํ•จ
    @NotBlank โ€โ€ (๊ณต๋ฐฑ) X, โ€ โ€œ(์ŠคํŽ˜์ด์Šค) X, ย 
    ๊ฒ€์ฆ ์‹คํŒจ์‹œ ์—๋Ÿฌ๋ฉ”์„ธ์ง€ ์ถœ๋ ฅ๋จ x ย 
    @NotNull โ€โ€, โ€œ โ€œ ๋‘˜ ๋‹ค ํ—ˆ์šฉ x
    @NotEmpty โ€โ€ (๊ณต๋ฐฑ)X, โ€œ โ€œ (์ŠคํŽ˜์ด์Šค)ํ—ˆ์šฉ x
    @Email ์œ ํšจํ•œ ์ด๋ฉ”์ผ ์ฃผ์†Œ์ธ์ง€๋ฅผ ๊ฒ€์ฆ x
    @Pattern(regexp=โ€~โ€) ์ •๊ทœํ‘œํ˜„์‹(~์•ˆ์— ๋“ค์–ด์™€์•ผ ํ•จ.), O
    @Min/ @Max ์ง€์ •๋œ ๊ฐ’ ์ดํ•˜/ ์ง€์ •๋œ ๊ฐ’ ์ด์ƒ์ด์–ด์•ผ ํ•จ. O
    @Range(min = , max = ) ๊ฐ’์ด min ์ด์ƒ, max ์ดํ•˜์—ฌ์•ผ ํ•จ(min๊ณผ max๋Š” long ํƒ€์ž…) ย 
    @Length(min = , max = ) ๋ฌธ์ž์—ด์˜ ๊ธธ์ด๊ฐ€ min ์ด์ƒ, max ์ดํ•˜์—ฌ์•ผ ํ•จ ย 
  3. ์œ ํšจ์„ฑ ๊ฒ€์ฆ์‹œ Controller Metnod์— ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
     @RestController
     @RequestMapping("/v1/members")
     @Validated
     public class MemberController {
         ...
     		...
        
         @PatchMapping("/{member-id}")
         public ResponseEntity patchMember(@PathVariable("member-id") @Min(2) long memberId,
                                         @Valid @RequestBody MemberPatchDto memberPatchDto) {
             memberPatchDto.setMemberId(memberId);
        
             // No need Business logic
        
             return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
         }
     }
    
    • @Valid @RequestBody ๋Š” ๊ผญ ์ž‘์„ฑํ•ด์•ผ ํ•จ. Valid ์ž‘์„ฑํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์Œ.
    • @PathVariable("member-id") long memberId ๋ณ€์ˆ˜๋Š” URI path์— ์‚ฌ์šฉ๋˜๋Š”๋ฐ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐ์ดํ„ฐ ์‹๋ณ„์ž๋Š” 0์ด์ƒ์˜ ์ˆซ์ž์ด๋ฏ€๋กœ @PathVariable(โ€œmember-idโ€) @Min(1) long memberId - ์ด๋Ÿฐ์‹์œผ๋กœ ์ œ์•ฝ์กฐ๊ฑด ๊ฑธ ์ˆ˜ ์žˆ์Œ.

      โ†’ PathVariable์— ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ์ •์ƒ์ ์œผ๋กœ ์ˆ˜ํ–‰๋˜๋ ค๋ฉด ํด๋ž˜์Šค ๋ ˆ๋ฒจ์— @Validated ๋ถ™์—ฌ์ค˜์•ผ ํ•จ.

  4. Jakarta Bean Validation์ด๋ž€?
    1. DTO ํด๋ž˜์Šค์˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•œ ์• ๋„ˆํ…Œ์ด์…˜์€ Jakarta Bean Validation์ด๋ผ๋Š” ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•œ ํ‘œ์ค€ ์ŠคํŽ™์—์„œ ์ง€์›ํ•˜๋Š” ๋‚ด์žฅ ์• ๋„ˆํ…Œ์ด์…˜
    2. Jakarta Bean Validation์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” API๊ฐ€ ์•„๋‹Œ ์ŠคํŽ™(์‚ฌ์–‘, Specification) ์ž์ฒด
    3. Jakarta Bean Validation ์ŠคํŽ™์„ ๊ตฌํ˜„ํ•œ ๊ตฌํ˜„์ฒด๊ฐ€ ๋ฐ”๋กœ Hibernate Validator
  5. Custom Validator๋ฅผ ์‚ฌ์šฉํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ
    • ๋ชฉ์ ์— ๋งž๋Š” ์• ๋„ˆํ…Œ์ด์…˜์ด ์—†์„๊ฒฝ์šฐ ์ง์ ‘ ๋งŒ๋“ค์–ด์„œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๊ฐ€๋Šฅ.
    • ๊ตฌํ˜„ ๊ณผ์ •
      1. Custom Validator๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ Custom Annotation์„ ์ •์˜
      2. ์ •์˜ํ•œ Custom Annotation์— ๋ฐ”์ธ๋”ฉ๋˜๋Š” Custom Validator๋ฅผ ๊ตฌํ˜„
      3. ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ํ•„์š”ํ•œ DTO ํด๋ž˜์Šค์˜ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜์— Custom Annotation์„ ์ถ”๊ฐ€


### comment

์˜ค๋Š˜์€ ์–ด์ œ๋ณด๋‹ค ๋‚˜์€๋“ฏ, dto ์–ด๋ ต๋‹ค๊ณ ํ•ด์„œ ์กฐ๊ธˆ ๊ฑฑ์ •ํ–ˆ๋Š”๋ฐ ๋‹คํ–‰์ด๋„ ์ดํ•ด๋ชปํ•  ์ •๋„๋Š” ์•„๋‹ˆ์˜€๋‹ค, ์‹ค์Šต๋„ ์–ด๋–ป๊ฒŒ๋“  ๊ตฌํ˜„ํ–ˆ๊ณ ! ๋ณต์Šตํ•˜๊ณ  DI๋ฅผ ๋‹ค์‹œ ๋ด์•ผํ•  ๊ฒƒ ๊ฐ™๋‹ค..DI๋Š” ์•„์ง๋„ ์ดํ•ด๋ชปํ•จ ์•„ ๊ทธ๋ฆฌ๊ณ  ์ •๊ทœ์‹โ€ฆ!์€ ํ•œ๋ฒˆ๋งŒ ๋ด์•ผ๊ฒ ๋‹ค..








๐Ÿ’ ๊ณต์ง€

- ์ •๋ณด ๊ณต์œ ๊ฐ€ ์•„๋‹Œ ๊ฐœ์ธ์ด ๊ณต๋ถ€ํ•˜๊ณ  ๊ธฐ๋กํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„์ž…๋‹ˆ๋‹ค.

Spring ์นดํ…Œ๊ณ ๋ฆฌ ๋‚ด ๋‹ค๋ฅธ ๊ธ€ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ

Leave a comment