DTO란 Data Transfer Object의 약자이며, 엔터프라이즈 애플리케이션 아키텍처 패턴 중 하나이다.
Transfer라는 이름에서 알 수 있듯이 데이터를 전송하기 위한 용도의 객체라고 할 수 있다.
클라이언트에서 서버로 전송하는 요청 데이터와 서버에서 클라이언트로 전송하는 응답 데이터의 형식으로, 클라이언트와 서버 간에 데이터 전송이 이루어진다고 학습했다. 이 구간에서 DTO를 사용할 수 있다.
@RestController
@RequestMapping("/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(@RequestParam("name") String name,
@RequestParam("email") String email,
@RequestParam("number") String number) {
Map<String, String> map = new HashMap<>();
map.put("name", name);
map.put("email", email);
map.put("number", number);
return new ResponseEntity<Map>(map, HttpStatus.CREATED);
}
...
...
}
DTO를 사용하기 전의 핸들러 메서드다. 회원 정보를 저장하기 위해 3개의 @RequestParam
애너테이션을 사용하고 있다.
실제로는 회원의 주소, 아이디, 패스워드 등 더 많은 정보가 포함되어 있을 수 있다. 그렇다면 postMember()
에 파라미터로 추가되는 @RequestParam
의 개수는 계속 늘어날 것이다.
이러한 경우 클라이언트 요청 데이터를 하나의 객체로 모두 전달 받을 수 있다면 코드가 간결해질 수 있다.
바로 DTO 클래스가 요청 데이터를 하나의 객체로 전달 받는 역할을 한다.
@RestController
@RequestMapping("/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(MemberDto memberDto) {
return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
}
...
...
}
postMember()
에서 @RequestParam
을 사용하는 부분이 사라지고 MemberDto memberDto
가 추가되었다.
@RequestParam
을 통해 전달 받은 요청 데이터들을 Map
에 추가하는 로직이 사라졌으며 MemberDto
객체를 ResponseEntity
클래스의 생성자 파라미터로 전달하도록 변경했다.
위에서 작성한 Controller의 핸들러 메서드는 클라이언트 요청 데이터에 대한 유효성 검증을 거치지 않았다.
클라이언트에서 회원 정보의 email 주소를 abc123@gmail.com
과 같은 형식이 아닌 abc123
과 같이 단순한 문자열로 전송해도 정상적으로 전달 받을 수 있다.
이와 같이 서버에서 유효한 데이터를 전달받기 위해 데이터를 검증하는 것을 유효성 검증이라 한다.
@RestController
@RequestMapping("/no-dto-validation/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(@RequestParam("name") String name,
@RequestParam("email") String email,
@RequestParam("number") String number) {
// (1) email 유효성 검증
if (!email.matches("^[a-zA-Z0-9_!#$%&'\\*+/=?{|}~^.-]+@[a-zA-Z0-9.-]+$")) {
throw new InvalidParameterException();
}
Map<String, String> map = new HashMap<>();
map.put("name", name);
map.put("email", email);
map.put("number", number);
return new ResponseEntity<Map>(map, HttpStatus.CREATED);
}
...
}
정규표현식을 사용해 이메일 주소의 유효성을 검증하는 로직이 핸들러 메서드 내에 직접적으로 포함되어 있는 것을 알 수 있다. name이나 number에 대한 유효성 검증도 필요하다면 핸들러 내의 코드는 유효성 검증 로직들이 가득해질 것이며 그만큼 코드 복잡도도 높아질 것이다.
결과적으로 이러한 방법은 좋지 않다.
핸들러 메서드 내부에 있는 유효성 검사 로직을 외부로 뺀다면 핸들러 메서드의 간결함을 유지할 수 있을 것 같다.
유효성 검증 로직을 DTO 클래스로 빼낸다면 핸들러 메서드의 간결함을 유지할 수 있을 것이다.
정규 표현식 더 알아보기
public class MemberDto {
@Email
private String email;
private String name;
private String number;
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 number;
}
public void setNumber(String number) {
this.phone = number;
}
}
위 예제는 MemberDto의 email 멤버 변수에 유효성 검증을 적용하는 코드다.
email 멤버 변수에 @Email
애너테이션을 추가하면 클라이언트의 요청 데이터에 유효한 이메일 주소가 포함되어 있지 않을 경우, 유효성 검증에 실패하게 되고 클라이언트 요청은 거부된다.
@RestController
@RequestMapping("/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(@Valid MemberDto memberDto) {
return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
}
...
}
MemberDto 클래스에서 이메일에 대한 유효성 검증을 진행하여 MemberController의 postMember()
핸들러 메서드는 간결해진 것을 볼 수 있다.
postMember()
핸들러 메서드의 파라미터인 MemberDto
앞에 붙은 @Valid
애너테이션은 MemberDto 객체에 유효성 검증을 적용하게 해주는 애너테이션이다.
@RequestBody
&@ResponseBody
JSON 형식의 Request Body를 전달 받으려면 DTO 객체에 @RequestBody
애너테이션을 붙여줘야 한다.
Respomse Body를 JSON 형식으로 전달하기 위해서는 @ResponseBody
애너테이션을 메서드 앞에 붙여줘야한다. 하지만 ResponseEntity
객체를 리턴 값으로 사용할 경우 @ResponseBody
를 생략할 수 있다.
직렬화, 역직렬화
서버쪽에서 클라이언트에게 응답 데이터를 전송하기 위해 DTO 같은 Java 객체를 JSON 형식으로 변환하는 것을 직렬화(Serialization)라고 한다. Java to JSON
클라이언트 쪽에서 JSON 형식의 데이터를 서버 쪽으로 전송하면 서버 쪽의 웹 애플리케이션은 전달 받은 JSON 형식의 데이터를 DTO 같은 Java 객체로 변환하는데, 이를 역직렬화(Deserialization)라고 한다. JSON to Java