HTTP 요청/응답에서의 DTO(Data Transfer Object)
by mignon25DTO(Data Transfer Object)란?
- 엔터프라이즈 애플리케이션 아키텍처 패턴의 하나
- 데이터를 전송하기 위한 용도의 객체
- 클라이언트의 요청 데이터, 서버의 응답 데이터에 사용될 수 있다.
DTO가 필요한 이유 (1) 코드가 간결해진다.
- 여러 정보를 전달 받아야 할 때, 각각의 데이터를 모두 각각의 파라미터로 전달받게 되면 코드가 복잡해진다.
- 그러한 파라미터들을 하나의 객체로 모은 DTO 클래스를 적용하면 다음과 같이 코드가 굉장히 간결해진다.
@RestController
@RequestMapping("/dto/members")
public class DTOMemberController {
@PostMapping
public ResponseEntity postMember(MemberDto memberDto) {
return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
}
}
DTO가 필요한 이유 (2) 데이터 유효성(Validation) 검증의 단순화
<DTO 미적용시 유효성 검증>
@PostMapping
public ResponseEntity postMember(
@RequestParam("email") String email,
@RequestParam("name") String name,
@RequestParam("phone") String phone
) {
// DTO 미적용 - email 유효성 검증
if(!email.matches("^[a-zA-Z0-9_!#$%&'\\*+/=?{|}~^.-]+@[a-zA-Z0-9.-]+$")) {
throw new InvalidParameterException();
}
...
}
- 유효성 검증 로직이 핸들러 메서드 내에 직접적으로 포함되어 있다.
- 다른 프로퍼티에 대한 유효성 검증도 필요하다면 핸들러 내의 코드는 유효성 검증 로직으로 넘쳐날 것이고, 그만큼 코드의 복잡도가 높아지게 된다.
HTTP 요청을 전달 받는 핸들러 메서드는 요청을 전달받는 것이 주 목적이기 때문에 최대한 간결하게 작성되는 것이 좋다.
<DTO 적용>
implementation 'org.springframework.boot:spring-boot-starter-validation'
@Getter
@Setter
public class MemberDto {
@Email
private String email;
private String name;
private String phone;
}
email 멤버 변수에 @Email 애너테이션을 추가하면 클라이언트의 요청 데이터에서 email이 유효한 형식이 아닐 경우 유효성 검증에 실패하고, 클라이언트의 요청은 거부된다.
@RestController
@RequestMapping("/dto/members")
public class DTOMemberController {
@PostMapping
public ResponseEntity postMember(@Valid MemberDto memberDto) { // @Valid 추가
return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
}
}
DTO에 유효성 검증을 적용함으로써 Controller의 코드가 위와 같이 깔끔해졌다.
<그 밖의 DTO 사용 목적>
- HTTP 요청의 수를 줄이기 위함 (유효하지 않은 요청은 아예 차단된다)
- 도메인 객체와의 분리
HTTP 요청/응답 데이터에 DTO 적용하기
(1) HTTP Request Body 가 JSON 형식이 아닌 경우
- x-www-form-urlencoded 형식

(2) HTTP Request Body 가 JSON 형식인 경우
<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, 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", "asdf@email.com");
body.put("name", "네임1");
body.put("phone", phone);
return new ResponseEntity<Map>(body, HttpStatus.OK);
}
// 1명의 회원 정보 조회
@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("MemberController.getMembers");
// not implementation
return new ResponseEntity<Map>(HttpStatus.OK);
}
// 회원 정보 삭제
@DeleteMapping("/{member-id}")
public ResponseEntity deleteMember(@PathVariable("member-id") long memberId) {
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
}
<Refactoring>
- 회원 정보를 전달받을 DTO 클래스를 생성한다.
- MemberController 에서 현재 회원 정보로 전달받는 각 데이터 항목(email, name, phone)들을 DTO 클래스의 멤버 변수로 추가
- 클라이언트 쪽에서 전달하는 요청 데이터를 @RequestParam 애너테이션으로 전달받는 핸들러 메서드를 찾는다.
- @RequestParam 쪽 코드를 DTO 클래스의 객체로 수정한다.
- Map 객체로 작성되어 있는 Response Body 를 DTO 클래스의 객체로 변경한다.
<MemberPostDto 및 MemberPatchDto 클래스 생성>
- DTO 클래스를 만들 때 getter 메서드가 반드시 있어야 한다.
- setter 메서드는 개발자의 필요에 따라 사용
@Setter
@Getter
public class MemberPostDto {
private String email;
private String name;
private String phone;
}
@Getter
@Setter
public class MemberPatchDto {
private long memberId;
private String name;
private String phone;
}
<MemberController에 DTO 적용>
@RestController
@RequestMapping("/dto/members")
public class DTOMemberController {
// 회원 정보 등록
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberPostDto) {
// 회원 등록 로직
return new ResponseEntity<MemberPostDto>(memberPostDto, HttpStatus.CREATED);
}
// 회원 정보 수정
@PatchMapping("/{member-id}")
public ResponseEntity patchMember(
@PathVariable("member-id") long memberId,
@Valid @RequestBody MemberPatchDto memberPatchDto) {
// 회원 정보 수정 로직
return new ResponseEntity<MemberPatchDto>(memberPatchDto, HttpStatus.OK);
}
// 한명의 회원 정보 조회
@GetMapping("/{member-id}")
public ResponseEntity getMember(@PathVariable("member-id") long memberId) {
System.out.println("DTOMemberController.getMember");
System.out.println("memberId = " + memberId);
return new ResponseEntity<>(HttpStatus.OK);
}
// 모든 회원 정보 조회
@GetMapping
public ResponseEntity getMembers() {
System.out.println("DTOMemberController.getMembers");
return new ResponseEntity<>(HttpStatus.OK);
}
// 회원 정보 삭제
@DeleteMapping("/{member-id}")
public ResponseEntity deleteMember(@PathVariable("member-id") long memberId) {
System.out.println("DTOMemberController.deleteMember");
System.out.println("memberId = " + memberId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
Additional Keywords
- 마틴 파울러(Martin Fowler)
- HttpMessageConverter 관련
'Spring' 카테고리의 다른 글
| [예외 처리] @ExceptionHandler를 이용한 예외 처리 (0) | 2023.04.20 |
|---|---|
| DTO 유효성 검증 (Validation) - (1) (0) | 2023.04.18 |
| Rest Client, RestTemplate (0) | 2023.04.17 |
| [Spring MVC] API 계층 적용하기 (Prac Project - 1. 커피 주문 애플리케이션 시작) (0) | 2023.04.16 |
| [Spring MVC] API 계층 (Prac Project - 2. ResponseEntity, Header) (0) | 2023.04.16 |
블로그의 정보
Mignon'S Dev Log
mignon25