티스토리 뷰

728x90
반응형

 

 

 

지난 글까지 컨트롤러 클래스의 구조를 잡고, 핸들러 메서드와 ResponseEntity까지 적용해 보았다.

 

2022.08.22 - [개발/Spring] - [Spring]Spring MVC - Controller 클래스에 ResponseEntity 적용

 

[Spring]Spring MVC - Controller 클래스에 ResponseEntity 적용

지난 글에서 컨트롤러 클래스를 작성하며 두 가지 개선할 부분을 지적했다. 수작업으로 JSON 문자열을 만들어 주는 부분 - 오타가 나기 쉽고 코드와 그 작성 시간이 길어진다. @RequestParam 애너테이

gnidinger.tistory.com

ResponseEntity를 적용하면서 지적했던 두 가지 개선사항 중 하나를 해결했는데,

 

  • 수작업으로 JSON 문자열을 만들어 주는 부분 - 오타가 나기 쉽고 코드와 그 작성 시간이 길어진다. → 해결
  • @RequestParam 애너테이션을 사용한 요청 파라미터 수신 - 파라미터가 많아질수록 코드와 그 작성 시간이 길어진다.

이번 글에선 DTO(Data Transfer Object)를 이용해 두 번째 개선사항을 해결한다.

 

들어가기 전에 먼저 DTO에 대해 간략히 알아보자.

 


 

DTO(Data Transfer Object)

 

DTO(Data Transfer Object - 데이터 전송 객체)는 말 그대로 프로세스 사이에서 데이터를 전송하는 객체이다.

 

여기서는 클라이언트-서버 사이의 요청/응답 데이터를 전송하는 객체를 말한다.

 

DTO를 사용하면 중요한 정보를 노출시키지 않고 두 시스템 간 통신을 원활하게 촉진할 수 있다는 장점이 있다.

 

DTO 클래스를 구성하는 일반적인 원칙은 다음과 같다.

 

  • DTO는 가능하면 POJO(Plain Old Java Objects)로 생성한다.
  • 순수하게 데이터를 저장하는 역할과, 데이터에 대한 getter(), setter() 메서드만을 가진다.
  • 저장, 검색, 직렬화, 역직렬화 로직을 제외한 어떠한 비즈니스 로직도 갖지 않는다.

    • 직렬화 - 서버가 DTO 같은 Java 객체를 JSON 형식으로 변환하는 것
    • 역직렬화 - 서버가 전달 받은 JSON 형식의 데이터를 DTO 같은 Java의 객체로 변환하는 것

여기서 우리가 DTO를 사용해 얻으려는 장점은 크게 세 가지가 있는데, 대략 아래와 같다.

 

  • 매개변수의 정리 및 코드의 간결성 확보 - postMember() 메서드에 파라미터로 @RequestParam이 계속 추가되는 것을 방지
  • Request Body(요청 데이터) 유효성(Validation) 검증의 단순화

    • 유효성 검증 - 서버 측에서 이메일 주소 규격(gni@gmail.com)과 같은 데이터의 유효성을 검증하는 것
    • ex)@Email을 추가하면 요청 데이터에 유효한 이메일 주소가 포함되어 있지 않을 경우 유효성 검증 실패
    • 유효성 검증 로직을 DTO 클래스로 빼내어 핸들러 메서드의 간결성 유지
  • 비용이 많이 드는 작업인 HTTP 요청 수 최소화 - 요청에 대한 모든 데이터를 저장할 수 있는 DTO를 사용

Controller 클래스의 HTTP 요청/응답 데이터에 DTO 적용

 

 

지난 글까지 요청 데이터(Request Body)는 위와 같은 ‘x-www-form-urlencoded’ 형식의 데이터였다.

 

지금부터 진행될 글에선 Request Body는 전부 JSON 형식으로 진행한다.

 

먼저 지난 시간까지 만들어 온 MemberController에 몇 가지 기능을 더해 불러오자.

package com.gnidinger.member;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@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> body = new HashMap<>();
        body.put("email", email);
        body.put("name", name);
        body.put("phone", phone);

        return new ResponseEntity<Map>(body, HttpStatus.CREATED);
    }

    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember(@PathVariable("member-id") long memberId,
                                      @RequestParam String phone) {
        Map<String, Object> body = new HashMap<>();
        body.put("memberId", memberId);
        body.put("email", "gni@gmail.com");
        body.put("name", "gnidinger");
        body.put("phone", phone);

        // No need Business logic
        return new ResponseEntity<Map>(body, HttpStatus.OK);
    }

    // 한명의 회원 정보 조회
    @GetMapping("/{member-id}")
    public ResponseEntity getMember(@PathVariable("member-id") long memberId) {
        System.out.println("# memberId: " + memberId);

        // not implementation
        return new ResponseEntity<Map>(HttpStatus.OK);
    }

    // 모든 회원 정보 조회
    @GetMapping
    public ResponseEntity getMembers() {
        System.out.println("# get Members");

        // not implementation
        return new ResponseEntity<Map>(HttpStatus.OK);
    }

    // 회원 정보 삭제
    @DeleteMapping("/{member-id}")
    public ResponseEntity deleteMember(@PathVariable("member-id") long memberId) {
        
        // No need business logic
        return new ResponseEntity(HttpStatus.NO_CONTENT);
    }
}

회원 정보 수정과 삭제에 관한 메서드가 추가된 것을 쉽게 확인할 수 있다.

 

이어서 DTO 적용을 위한 리팩터링 절차는 다음과 같다.

 

  • 회원 정보를 전달 받을 DTO 클래스 생성

    • MemberCotroller의  데이터 항목(email, name, phone) DTO 클래스의 멤버 변수로 추가
  • 클라이언트 쪽에서 전달하는 요청 데이터를 @RequestParam 애너테이션으로 전달받는 핸들러 메서드 찾기

    • Request Body 필요한 핸들러는 HTTP POST, PATCH, PUT 같이 리소스의 추가나 변경이 발생하는 경우
    • HTTP GET 리소스를 조회만 하는 용도이기 때문에 Request Body가 필요 없음
    • @PostMapping, @PatchMapping 애너테이션이 붙은 핸들러 메서드를 찾는 과정과 동일
  • @RequestParam 쪽 코드를 DTO 클래스의 객체로 수정
  • Map 객체로 작성되어 있는 Response Body DTO 클래스의 객체로 변경

한 단계씩 순서대로 살펴보자.

 

  • DTO 클래스 생성

회원 정보 등록과 수정을 위한 MemberPostDto 및 MemberPatchDto 클래스를 각각 생성한다.

package com.gnidinger.member;

public class MemberPostDto {
    private String email;
    private String name;
    private String phone;

    public String getEmail() {
        return email;
    }

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }
}
package com.gnidinger.member;

public class MemberPatchDto {
    private long memberId;
    private String email;
    private String name;
    private String phone;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public long getMemberId() {
        return memberId;
    }

    public void setMemberId(long memberId) {
        this.memberId = memberId;
    }
}

위에서 알아본 바와 같이 순수하게 데이터를 저장하는 역할과, 데이터에 대한 getter(), setter() 메서드만을 가졌으며

 

 데이터 항목(email, name, phone)을 멤버 변수로 가진 클래스임을 확인할 수 있다.

 

특히 getter 메서드가 없으면 Response Body에 해당 값이 포함되지 않는 문제가 발생하기 때문에 주의해야 한다.

 

  • MemberController에 DTO 적용
package com.gnidinger.member;

import com.gnidinger.member.MemberPatchDto;
import com.gnidinger.member.MemberPostDto;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/v1/members")
public class MemberController {
    // 회원 정보 등록
    @PostMapping
    public ResponseEntity postMember(@RequestBody MemberPostDto memberPostDto) {
        return new ResponseEntity<>(memberPostDto, HttpStatus.CREATED);
    }

    // 회원 정보 수정
    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember(@PathVariable("member-id") long memberId,
                                      @RequestBody MemberPatchDto memberPatchDto) {
        memberPatchDto.setMemberId(memberId);
        memberPatchDto.setEmail("gni@gmail.com");
        memberPatchDto.setName("gnidinger");
        
        // No need Business logic
        return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
    }

    // 한명의 회원 정보 조회
    @GetMapping("/{member-id}")
    public ResponseEntity getMember(@PathVariable("member-id") long memberId) {
        System.out.println("# memberId: " + memberId);

        // not implementation
        return new ResponseEntity<>(HttpStatus.OK);
    }

    // 모든 회원 정보 조회
    @GetMapping
    public ResponseEntity getMembers() {
        System.out.println("# get Members");

        // not implementation
        return new ResponseEntity<>(HttpStatus.OK);
    }

    // 회원 정보 삭제
    @DeleteMapping("/{member-id}")
    public ResponseEntity deleteMember(@PathVariable("member-id") long memberId) {
        
        // No need business logic
        return new ResponseEntity(HttpStatus.NO_CONTENT);
    }
}

여러 개의 @RequestParam을 사용하는 대신

 

  • postMember() 메서드에서는 MemberPostDto 클래스의 객체를,
  • patchMember() 메서드에서는 MemberPatchDto 클래스의 객체를

이용해 Request Body를 한 번에 전달 받음으로써 코드의 간결성을 확보했다.

 


  • @RequestBody - JSON 형식의 Request Body를 DTO 클래스의 객체로 변환(역직렬화), DTO 객체에 붙인다.
  • @ResponseBody - DTO 클래스의 객체를 JSON 형식의 Response Body로 변환(직렬화), 클래스와 메서드에 붙인다.

    • 핸들러 메서드에 @ResponseBody가 붙으면 내부적으로 HttpMessageConverter가 동작해 응답 객체(여기서는 DTO 클래스의 객체)를 자동으로 JSON 형식으로 바꿔줌.
    • 또는 리턴 값이 ResponseEntity일 경우에도 자동으로 바꿔줌 -> @ResponseBody 명시할 필요 없음

위 그림은 JSON 형식을 통해 postMember() 핸들러 메서드를 호출한 결과이다.

 

위 코드에서 전달받는 HTTP Request Body가 JSON 형식이어야 하기 때문에 

 

클라이언트 쪽에서 전달하는 Request Body 역시 JSON 형식으로 입력을 해야 한다.

 

이어서 같은 방식으로 patchMember() 메서드를 호출해 보자.

 

비슷한 방식을 이용해 CoffeeController와 OrderController에도 DTO를 적용할 수 있다.

 

마지막으로 지금까지 적용한 DTO 방식의 단점을 짚고 글을 마치자.

 

이 글에서 살펴본 DTO 클래스는 MemberController에 해당되는 MemberPostDto와 MemberPatchDto 클래스이다.

 

CoffeeController와 OrderController까지 포함하면 DTO 적용을 위해 클래스가 6개가 필요한데,

 

컨트롤러 클래스가 늘어날수록 그 두 배만큼 DTO 클래스가 늘어난다는 말이 된다.

 

이렇게 늘어나는 DTO 클래스의 작성이 DTO 적용의 단점이라고 할 수 있다.

 

계속해서 다음 글에선 DTO 유효성 검증을 구현하는 법에 대해서 알아보자.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함