DTO(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) 데이터 검증의 단순화
@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 요청을 받는 핸들러 메서드는 요청을 받는 것이 주 목적이므로 최대한 간결하게 작성하는 것이 좋다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
@Getter
@Setter
public class MemberDto {
@Email
private String email;
private String name;
private String phone;
}
이메일 멤버 변수에 @Email 주석을 추가한 경우 이메일이 클라이언트의 요청 데이터에서 유효한 형식이 아닌 경우 유효성 검사가 실패하고 클라이언트의 요청이 거부됩니다.
@RestController
@RequestMapping("/dto/members")
public class DTOMemberController {
@PostMapping
public ResponseEntity postMember(@Valid MemberDto memberDto) { // @Valid 추가
return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
}
}
DTO에 유효성 검사를 적용하면 위와 같이 컨트롤러 코드가 더 깔끔해집니다.
- HTTP 요청 수를 줄이기 위해(잘못된 요청은 모두 차단됨)
- 도메인 개체와의 분리
HTTP 요청/응답 데이터에 DTO 적용
(1) HTTP 요청 본문이 JSON 형식이 아닌 경우
- x-www-form-urlencoded 형식
(2) HTTP 요청 본문이 JSON 형식인 경우
@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", "[email protected]");
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);
}
}
- 구성원 정보를 받을 DTO 클래스를 만듭니다.
- MemberController로부터 현재 멤버 정보로 받은 각 데이터 항목(이메일, 이름, 전화번호)을 DTO 클래스의 멤버 변수로 추가
- @RequestParam 어노테이션을 통해 클라이언트 측에서 전송한 요청 데이터를 받는 핸들러 메서드를 찾습니다.
- @RequestParam 측의 코드를 DTO 클래스의 객체로 수정합니다.
- Map 객체로 작성된 Response Body를 DTO 클래스의 객체로 변경합니다.
- 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;
}
@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);
}
}
추가 키워드
- 마틴 파울러
- HttpMessageConverter 관련