랩터
[Spring MVC] DTO(Data Transfer Object) 본문
이번 포스팅에서는 DTO(Data Transfer Object)가 무엇인지 알아보고, 클라이언트의 요청을 DTO로 변환한 후에 이 DTO를 다시 응답으로 변환하는 방법을 살펴보도록 하겠습니다.
그리고 클라이언트의 요청 데이터에 Validation(유효성 검증)을 적용하여 요청 데이터의 안전성을 보장하는 방법 또한 살펴보도록 하겠습니다.
학습 목표
- DTO(Data Transfer Object)
- DTO가 무엇인지 이해할 수 있다.
- DTO를 Controller 클래스에 적용할 수 있다.
- DTO Validation이 무엇인지 이해할 수 있다.
DTO(Data Transfer Object)란?
DTO란 무엇일까요? DTO는 Data Transfer Object의 약자로 마틴 파울러가 소개한 애플리케이션 아키텍처 패턴의 하나입니다. 데이터를 전송하기 위한 용도의 객체 정도로 생각하면 됩니다. 데이터 전송은 클라이언트->서버 인 요청데이터,
서버->클라이언트 인 응답 데이터 형식으로 클라이언트와 서버 간에 데이터 전송이 이루어집니다.
DTO가 필요한 이유
DTO가 필요한 이유는 뭘까요?
1. DTO 클래스를 이용한 코드의 간결성
@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);
}
...
...
}
위 코드는 postMember()핸들러메서드입니다. 회원정보를 저장하기 위해서 총 세 개의 @RequestParam애너테이션을 사용하고있습니다. 요청 데이터가 세개밖에없지만 실제로는 더 많은 회원 정보들이 요구 될수 있습니다. 따라서 @RequestParam의 개수는 계속 늘어날 수밖에 없습니다. 이경우에, 클라이언트의 요청 데이터를 하나의 객체로 모두 전달받을 수 있다면 코드 자체가 굉장히 간결해집니다.
DTO클래스가 바로 요청 데이터를 하나의 객체로 전달받는 역할을 해줍니다.
@RestController
@RequestMapping("/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(MemberDto memberDto) {
return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
}
...
...
}
위 코드는 @RequestParam부분이 사라지고 MemberDto memberDto가 추가되었습니다. @RequestParam을 통해 Map에 추가하는 로직이 사라지고 MemberDto객체를 ResponseEntity클래스의 파라미터로 전달하도록 변경되었습니다.
2. 데이터 유효성(Validation) 검증의 단순화
서버 쪽에서 유효한 데이터를 전달받기 위해 데이터를 검증하는 것을 유효성(Validation) 검증이라고 합니다.
@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);
}
...
...
}
위 코드는 name이나 phone에 대한 유효성 검증도 필요하다면 핸들러 내의 코드는 유효성을 검증하는 로직들이 넘쳐나고 그만큼 코드의 복잡도도 높아지게 됩니다.
핸들러 메서드 내부에 있는 유효성 검사 로직을 외부로 뺄 수 있다면 핸들러 메서드의 간결함을 유지할 수 있을 것 같습니다.
이럴 때, DTO 클래스를 사용하면 유효성 검증 로직을 DTO 클래스로 빼내어 핸들러 메서드의 간결함을 유지할 수 있습니다.
package com.springboot.V2.member;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.Email;
@Getter
@Setter
public class MemberDto {
@Email
private String email;
private String name;
private String phone;
}
email멤버 변수에 유효성 검증을 적용하는 예제 코드입니다. email멤버 변수에 @Email 애너테이션을 추가하면 클라이언트의 요청 데이터에 유효한 이메일 주소가 포함되어 있지 않을 경우 유효성 검증에 실패하기 때문에 클라이언트 요청은 거부됩니다. 이 클래스에서 유효성 검증을 진행하므로 핸들러메서드 코드는 아래와 같이 간결해집니다.
@RestController
@RequestMapping("/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(@Valid MemberDto memberDto) {
return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
}
...
...
}
postMember() 핸들러 메서드의 파라미터인 MemberDto 앞에 붙은 @Valid 애너테이션은 MemberDto 객체에 유효성 검증을 적용하게 해주는 애너테이션입니다.
DTO 유효성 검증(Validation)이 필요한 이유
DTO 클래스의 유효성 검증은 왜 필요할까요?
프론트엔드 쪽에서 유효성 검증을 하는데 굳이 백엔드 애플리케이션 쪽에서 유효성 검증을 할 필요가 있나 싶지만 프론트엔드 쪽에서 유효성 검사에 통과되었다고 그 값이 반드시 유효한 값이라고 보장할 수 없습니다.
DTO 클래스에 유효성 검증 적용하기
MemberController에서 사용된 MemberPostDto클래스와 MemberPatchDto클래스에 유효성 검증을 적용해보겠습니다.
먼저 build.gradle 파일의 dependencies 항목에 'org.springframework.boot:spring-boot-starter-validation’을 추가하세요.
MemberPostDto 유효성 검증
✔️ MemberPostDto 유효성 검증 제약 사항
- email (이메일 주소)
- 값이 비어있지 않거나 공백이 아니어야 합니다.
- 유효한 이메일 주소 형식이어야 합니다
- name (이름)
- 값이 비어있지 않거나 공백이 아니어야 합니다.
- phone (휴대폰 번호)
- 값이 비어있지 않거나 공백이 아니어야 합니다.
- 아래와 같이 010으로 시작하는 11자리 숫자와 ‘-’로 구성된 문자열이어야 합니다.
- 예) 010-1234-5678
package com.springboot.V2.member;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
@Getter
@Setter
public class MemberPostDto {
@NotBlank
@Email
private String email;
@NotBlank(message = "이름은 공백이 아니어야 합니다.")
private String name;
@Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
message = "휴대폰 번호는 010으로 시작하는 11자리 숫자와 '-'로 구성되어야 합니다.")
private String phone;
}
MemberPostDto의 멤버 변수에 적용된 유효성 검증 내용은 다음과 같습니다.
- email
- @NotBlank
- 이메일 정보가 비어있지 않은지를 검증합니다.
- null 값이나 공백(””), 스페이스(” “) 같은 값들을 모두 허용하지 않습니다.
- 유효성 검증에 실패하면 에러 메시지가 콘솔에 출력됩니다.
- @Email
- 유효한 이메일 주소인지를 검증합니다.
- 유효성 검증에 실패하면 내장된 디폴트 에러 메시지가 콘솔에 출력됩니다.
- @NotBlank
- name
- @NotBlank
- 이름 정보가 비어있지 않은지를 검증합니다.
- null 값이나 공백(””), 스페이스(” “) 같은 값들을 모두 허용하지 않습니다.
- 유효성 검증에 실패하면 @NotBlank의 message 애트리뷰트에 지정한 문자열이 에러 메시지로 콘솔에 출력됩니다.
- @NotBlank
- phone
- @Pattern
- 휴대폰 정보가 정규표현식(Reqular Expression)에 매치되는 유효한 번호인지를 검증합니다.
- 유효성 검증에 실패하면 내장된 디폴트 에러 메시지가 콘솔에 출력됩니다.
- @Pattern
Postman으로 post단에
이렇게 요청을 보내면 응답 결과는 Response Satus가 400인 ‘Bad Request’를 전달받았습니다.
MemberPatchDto 유효성 검증
✔️ MemberPatchDto 유효성 검증 제약 사항
- name (이름)
- 값이 비어있을 수 있습니다.
- 값이 비어있지 않다면 공백이 아니어야 합니다.
- phone (휴대폰 번호)
- 값이 비어있을 수 있습니다.
- 아래와 같이 010으로 시작하는 11자리 숫자와 ‘-’로 구성된 문자열이어야 합니다.
- 예) 010-1234-5678
package com.springboot.V2.member;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.Pattern;
@Getter
@Setter
public class MemberPatchDto {
private long memberId;
@Pattern(regexp = "^\\S+(\\s?\\S+)*$", message = "회원 이름은 공백이 아니어야 합니다.")
private String name;
@Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
message = "휴대폰 번호는 010으로 시작하는 11자리 숫자와 '-'로 구성되어야 합니다.")
private String phone;
}
MemberPatchDto클래스의 멤버 변수에 적용된 유효성 검증내용은 다음과 같습니다.
- memberId
- Request Body에 포함되는 데이터가 아니므로 유효성 검증이 필요하지 않습니다.
- name
- @Pattern
- 정규 표현식으로 다음 내용을 체크합니다.
- 이름 정보가 비어있으면(null) 유효성 검증을 하지 않습니다.
- 이름 정보가 비어 있지 않고(not null), 공백 문자열이라면 검증에 실패합니다.
- 시작 문자가 공백이면 검증에 실패합니다.
- 끝 문자가 공백이면 검증에 실패합니다.
- 문자와 문자 사이 공백이 1개를 초과하면 검증에 실패합니다.
- 정규 표현식으로 다음 내용을 체크합니다.
- @Pattern
- phone
- @Pattern
- 정규 표현식으로 다음 내용을 체크합니다.
- 휴대폰 정보가 비어있으면(null) 유효성 검증을 하지 않습니다.
- 휴대폰 정보가 비어 있지 않고, 010으로 시작하는 11자리 숫자와 ‘-’로 구성된 문자열이 아니라면 검증에 실패합니다.
- 정규 표현식으로 다음 내용을 체크합니다.
- @Pattern
유효성 검증 에너테이션을 추가한 MemberPatchDto클래스를 사용하는 patchMember()코드입니다.
@RestController
@RequestMapping("/v1/members")
@Validated
public class MemberController {
...
...
@PatchMapping("/{member-id}")
public ResponseEntity patchMember(@PathVariable("member-id") @Min(1) long memberId,
@Valid @RequestBody MemberPatchDto memberPatchDto) {
memberPatchDto.setMemberId(memberId);
// No need Business logic
return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
}
}
쿼리 파라미터(Query Parameter 또는 Query String) 및 @Pathvariable에 대한 유효성 검증
일반적으로 수정이 필요한 데이터의 식별자는 0 이상의 숫자로 표현을 합니다.
patchMember() 핸들러 메서드에서 사용되는 memberId에 ‘1 이상의 숫자여야 한다’라는 제약 조건을 걸어보도록 하겠습니다.
위 코드에서 @PathVariable("member-id") @Min(1) long memberId에 1이상의 숫자일 경우에만 유효성 검증에 통과하도록 @Min(1)이라는 검증 애너테이션을 추가했습니다.
memberId에 0을 넣었더니 오류가 뜨는것을 확인할수 있습니다.
'공부 > Spring' 카테고리의 다른 글
[Spring MVC] Spring MVC 아키텍처 (0) | 2024.07.11 |
---|