SKSDUD

[Spring] - DTO(Data Transfer Object) 본문

CodeStates/Section 03

[Spring] - DTO(Data Transfer Object)

NYinJP 2023. 2. 16. 08:21

학습 목표

  • DTO가 무엇이니?
  • DTO를 Controller 클래스에 적용할 수 있니?
  • DTO Validation이 무엇이니?

DTO란?

Data Transfer Object의 두문자어로 데이터를 전송하기 위한 용도의 객체를 뜻한다. 

데이터 전송은 언제 이루어지는가? 서버가 클라이언트에게 응답 데이터를 보내줄 때, 클라이언트가 서버에게 요청 데이터를 보낼 때 데이터 전송이 이루어집니다.

이 구간에서 DTO 클래스를 사용합니다!

 

이전에는

@RequestParam 애너테이션을 사용해서 클라이언트 요청 데이터를 각각 받아왔지만, 이 요청 데이터가 많아진다면?

클라이언트의 요청 데이터를 객체 하나로 전달받는 방식이 훨씬 가독성이 좋고 편리할 것입니다.

또한 서버 쪽에서 유효한 데이터를 받기 위해 데이터를 검증하는 것을 유효성(Validation) 검증이라 하는데, DTO 클래스를 사용하면 매우 쉽다.

@PostMapping
public ResponseEntity postMember(MemberDto memberDto) {
    return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
}

postMember의 매개변수로 MemberDto 클래스의 객체가 들어갑니다. 

클라이언트의 요청 데이터들을 저 객체에 모두 저장하여 Controller 핸들러 메서드에게 보내줍니다.

@RequestParam 애너테이션을 통해 정보를 각각 받아오는 것보다 훨씬 가독성이 좋고 편리합니다.

또한 서버쪽에서 유효한 데이터를 전달받기 위해 데이터를 검증하는 것을 유효성(Validation) 검증이라 하는데

여러 유효성 검사를 하는 로직들을 DTO 클래스로 빼냄으로써 핸들러 메서드의 본연의 역할을 할 수 있습니다.

  • 요청 데이터 처리 코드의 간결화
  • 데이터 유효성 검증의 단순화

앞으로 개발을 할 때  JSON으로 통신하게 될 것이다. 프론트엔드와 백엔드, 클라이언트와 백엔드.

@RequestBody 애너테이션을 붙일 것. 

 

필요성


 

나의 부족한 기본 개념

💬 SSR과 CSR이란?
SSR(Server Side Rendering) 서버에서 페이지 렌더링
CSR(Client Side Rendering) 클라이언트(브라우저)에서 페이지 렌더링
렌더링이란? 브라우저에 우리가 보는 화면을 만들어내는 과정

💬 @RequestBody 애너테이션
http 요청의 본문이 그대로 전달된다. 일반적인 GET/POST의 요청 파라미터라면 사용할 일이 없겠지만, JSON 기반의 메시지를 사용하는 요청의 경우에 매우 유용하다고 한다. Controller 핸들러 메서드에서 입력받은 DTO 객체 앞에 붙여준다. 요렇게 붙여주면 클라이언트로부터 입력받은 JSON 형식의 Request Body를 DTO 객체로 변환해 준다! 클라이언트는 꼭 JSON 형식으로 데이터를 보내주어야 한다! 다른 형식의 데이터를 보내면
'Unsupported Media Type과 같은 에러 메시지를 포함한 응답을 전달한다.
postMember(@RequestBody MemberPostDto memberPostDto)

💬 @ResponseBody 애너테이션
요놈은 또 무엇일까? RequestBody 애너테이션과 정반대의 역할을 한다! JSON 형식인 Response Body를 위해 DTO 클래스의 객체를 JSON으로 바꾸어 주는 역할을 한다. 그런데 우리 실습에서는 핸들러 메서드의 반환값을 ResponseEntity 객체로 반환하고 있다. 이때는 알아서 JSON으로 바꾸어준다고 한다.  

💬 직렬화 VS 역직렬화
(클라이언트) JSON ➡ (서버) 자바 객체 :: 역직렬화
(서버) 자바 객체 ➡ (클라이언트) JSON :: 직렬화
JSON➡DTO : 역직렬화(Deserialization)
@PostMapping
    public ResponseEntity postMember(@RequestBody MemberPostDto memberPostDto) {
        return new ResponseEntity<>(memberPostDto, HttpStatus.CREATED);
    }

 

 

 


Controller 클래스 레거시 코드

DTO 클래스 통해 리팩토링 하기

// 레거시 코드
    @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", "hgd@gmail.com");
        body.put("name", "홍길동");
        body.put("phone", phone);

        // No need Business logic

        return new ResponseEntity<Map>(body, HttpStatus.OK);
    }
  1. DTO 클래스 만들기
    1. 클라이언트 요청에 따른 Request Body 처리 방법이다.  
    2. Controller에서 현재 @RequestParam으로 입력받는 각 데이터 항목들을 DTO 클래스의 멤버변수로 추가한다.
    3. @PostMapping, @PatchMapping과 같은 애너테이션이 붙은 클라이언트의 요청 데이터를 받는 핸들러 메서드를 찾으면 된다.
  2. Response Body
    1. 현재 Map 객체인 Response Body를 DTO 클래스 객체로 변경한다.

결론적으로, Member 패키지 안에는 dto 패키지가 존재하고

회원 정보 등록에 사용되는 MemberPostDto(회원 정보 등록에 사용되는 데이터항목들이 멤버 변수로 +Get/Setter)

회원 정보 수정에 사용되는 MemberPatchDto(회원 정보 수정에 사용되는 데이터항목들이 멤버변수로 +Get/Setter)

가 있게 된다. 

 

게터는 필수이나 세터는 선택이다. 

게터, 세터 메서드는 클래스 레벨에서 lombok의 @Getter, @Setter 애너테이션을 사용하면 편리하다.

 

이제 만들어진 DTO 클래스를 Controller 클래스에 적용해 보자!

@RequestParam으로 데이터 항목들을 일일이 받았던 것을 수정해

postMember - PostMemberDto

patchMember - PatchMemberDto

각 메서드에 필요한 데이터를 DTO 클래스의 객체를 이용해 Request Body를 한 번에 받고 있다. 

 

CoffeeController로 실습해 보기

1. 왜 DTO 클래스를 썼는지 생각해 보라!

  • 핸들러 메서드에서 @RequestParam 애너테이션을 통해 각각 데이터들을 받아오는 게 힘들어서 클라이언트의 Request Body를 저장할 클래스를 만든 것이다.
  • 따라서 레거시 코드를 살펴보면 핸들러 메서드에 @PatchMapping, @PostMapping 애너테이션이 붙여진 것들이 있다. 이 경우가 리소스의 추가나 변경이 일어나는 지점으로 데이터 전송이 오고 가는 지점이다! 

2. @PostMapping, @PatchMapping 데이터 항목 살펴보기!

  • DTO 클래스의 멤버 변수로 만들어준다.
  • 게터 세터는 @Getter @Setter로 편리하게 추가해 준다.

3. 결국 DTO 클래스 멤버변수들의 의미는?

  • 어디까지나 내가 이해한 방향이다. 먼저 클라이언트가 Coffee 정보 추가하기 위해 JSON 데이터를 PostMapping 핸들러 메서드에 전달한다. postCoffee 메서드에 들어갔을 때 @RequestBody 애너테이션이 이 JSON 데이터를 coffeePostDTo 객체의 인스턴스로 만들어준다. 데이터가 한꺼번에 객체 안에 저장되어 핸들러 메서드에서 사용되는 것이다. 결론적으로! PostDto 객체는 Coffee 정보에 추가하고 싶은 데이터를 담고 있어야 한다.
  • 같은 맥락으로 patchCoffeeDto는 수정하고 싶은 정보를 담고 있는 것이다. 가격만 수정하고 싶기 때문에 price 만 변수로 추가해 주었다. 
  • 어떤 상황에서 어떤 애너테이션을 쓰는지 잘 알아둬야 한다.   

 

⬇: 역직렬화         ⬆: 직렬화

아직 DB와 연동하지 않았기 때문에 setter로 각 값들을 넣어줘야 한다. 아니면 null값이 나오게 될 것이다. 

price는 클라이언트로부터 요청받은 값을 DTO 객체에 저장해 주었다.

JSON이 기준이다. JSON이 되면 직렬화 반대면 역직렬화

 


데이터 유효성 검증(Validation)

프론트엔드에서 한번 자바스크립트를 이용해서 사용자가 입력하는 입력 폼 필드의 값에 대해 1차적으로 유효성 검증을 해준다. 그리고 잘못 입력했다고 사용자에게 알려준다. 부수적으로 백엔드 서버쪽에서도 유효성 검증을 진행하는 이유는? 

프론트엔드 유효성 검증을 통과했다고 해서 그 값이 반드시 유효한 값은 아니다. 값을 조작할 수 있는 여지가 충분하기 때문에 서버에서도 반드시 한번 더 유효성 검증을 해줘야 한다. 

  • 프론트엔드에서의 유효성 검증 : 사용자 편의 측면
  • 백엔드에서의 유효성 검증 : 애플리케이션 동작의 신뢰성 측면

 

DTO 클래스 유효성 검증 제약사항 예

  • email (이메일 주소)
    • 값이 비어있지 않거나 공백이 아니어야 합니다. @NotBlank  
    • 유효한 이메일 주소 형식이어야 합니다 @Email
  • name (이름)
    • 값이 비어있지 않거나 공백이 아니어야 합니다. @NotBlank
  • phone (휴대폰 번호)
    • 값이 비어있지 않거나 공백이 아니어야 합니다. @NotBlank
    • 아래와 같이 010으로 시작하는 11자리 숫자와 ‘-’로 구성된 문자열이어야 합니다. 정규표현식
      • 예) 010-1234-5678

 

그 외의 다양한 애너테이션들!

@Range(min= , max= )
이 주석이 달린 요소는 적절한 범위 내에 있어야 합니다.
계속 추가!

 

 

위의 애너테이션들을 DTO 클래스에 적절히 적용하면 된다. 

@Pattern(regexp="정규표현식") 애너테이션이 있는데 유효성 검증에 정규표현식이랑 같이 쓰면 아주 쉽고 편하다!

 

유효성 검증 로직이 실행되게 하기 위해서는 Controller에서 DTO 클래스에 @Valid 애너테이션을 붙여야 한다. 

유효성 검증에 실패하면 로그를 확인하여 알 수 있다. 

💬 정규표현식 뽀개기!

. ^ $ * + ? {} [] \ | ()
정규표현식은 위와 같은 메타문자를 사용한다. 메타문자는 문자의 의미를 설명하기 위한 문자로, 문자의 구성을 설명하기 위해 원래 의미가 아닌 다른 의미로 쓰이는 문자를 말합니다. 

1. 반복 관련 메타문자 * + ? {}
학습 콘텐츠에서는 어렵게 설명했는데 사실 별거 없다. 그냥 바로 앞에 문자의 반복을 의미하는 메타문자이다.
* : 반복 횟수가 0부터 시작 무한대~
+ : 반복 횟수가 1부터 시작
? : 반복은 아니지만 비슷한 개념으로 0 또는 1을 의미한다. 있거나 없거나
{} : 원하는 반복 횟수가 있을 때 사용한다. 
  {n} : 반드시 n번 반복
  {m, n} : 반드시 m~n회 반복
  {m,}, {, n} : m회 이상 n회 이하 반복

2. 역슬래시 
\d : 숫자와 매치
\D : 숫자가 아닌 것과 매치
\s : 공백 문자열
\S : 공백이 아닌 나머지 문자열
\w : 문자+숫자와 매치
\W :문자 + 숫자가 아닌 것과 매치

3. 그 외
^ : 문자열의 시작
$ : 문자열의 끝
[] : [] 사이의 문자들과 매치 [] 안에서 ^는 반대(not) 의미로 쓰인다. []가 아니면 본래 뜻!
  [abc] : "a, b, c 중 한 개의 문자와 매치"
- : 사이의 범위
\ : 이스케이프 문자

💭 연습문제 1
"^\\S+(\\s?\\S+)*$"

 

^, $ : 문자열의 시작과 끝
\\s : 앞의 \는 이스케이프 문자로 \s가 제 역할을 하도록 붙여주었다. 

\\S 는 시작문자. 문자열 + 메타문자로 인해 반드시 있어야 한다. (시작 문자로 공백이 들어오면 유효성 검사 실패)
괄호로 묶인 () 뒤에 *가 있다. 0부터 시작하는 반복자. 없어도 된다는 뜻! (끝 문자로 공백이 들어오면 실패)
괄호 안에 \\s 공백문자열 뒤에 ? 가 있어 공백이 있어도 되고 없어도 된다.(공백이 1개를 초과하면 실패)
\\S는 문자열로 + 반드시 하나 이상 있어야 한다. 괄호 끝!

💭 연습문제 2
"^010-\\d{3,4}-\\d{4}$"

 

시작문자 010-  
\d : 숫자 3개 또는 4개
\d : 숫자 4개

010으로 시작하고 -으로 구분되는 10~11자리 전화번호를 작성하면 됩니다.

💭 연습문제 3
이메일 유효성 검증을 위한 정규표현식을 작성해보아라.
ㅇㅇ

 

쿼리 파라미터 유효성 검증

@PathVariable 애너테이션에도 유효성 검증 제약조건을 걸 수 있습니다. 

그런데! @PathVariable 애너테이션에 붙은 유효성 검증 로직을 실행하려면 클래스 단계에서

@Validated 애너테이션을 붙여줘야 합니다. 

URI 파라미터로 1이상 값이 들어오지 않으면 유효성 검증 실패

Custom Validator 도 있다고 하네요.

 


실습 100% 이해해 보기

CoffeeController 클래스에 DTO 클래스를 적용해 봅니다. 

DTO 클래스에 유효성 검증 로직을 다 구현해 놓고

정작 @RequestBody가 붙은  DTO 객체와 클래스에 @Valid 애너테이션을 붙이지 않으면 의미가 없습니다.

꼭 기억하기! 유효성 검증 로직을 실행하게 하는 애너테이션 @Valid

정규표현식을 차분히 정리해 보길 잘했다. 정규표현식 쓰는 문제였다!

 

 

@Pattern(regexp = "^\\S+\\s?\\S+$",
        message = "공백이나 빈 값이면 안됩니다.")
private String korName;

근데 여기서 자꾸 에러가 난다.

난 값이 들어갈 수 있다고 생각했는데

뭐가 문제인지 다시 생각!!!

 

 


레퍼런스 코드는 다양한 정답 중 하나일 뿐이다. 

'CodeStates > Section 03' 카테고리의 다른 글

[Spring] JPA  (0) 2023.02.25
[Spring] 예외 처리  (0) 2023.02.16
DI 줌 세션 설명 받아적은 것  (0) 2023.02.16
[Spring] Spring MVC - 서비스 계층  (0) 2023.02.16
[Spring] API 계층 - Controller  (0) 2023.02.14