DTO

​ DTO 란 Data Transfer Object의 약자로 데이터를 전송하기 위한 용도의 객체입니다. DTO 는 코드의 간결성, 유효성 검증의 단순화를 위해서 사용합니다.

​ 보통 DTO 는 @RequestBody 어노테이션과 사용하는데요. @RequestBody 는 HTTP 메서드의 Body 값의 JSON 을 사용한다는 뜻입니다. JSON 형식의 Request Body를 MemberPostDto 클래스의 객체로 변환을 시켜주는 역할을 합니다. 이를 JSON 역직렬화(JSON -> java 객체) 라고 합니다.

@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);
}

반대로 @ResponseBody 는 JSON 형식의 Response Body를 클라이언트에게 전달하기 위해 DTO 클래스의 객체를 Response Body로 변환하는 역할을 합니다. Spring MVC에서는 핸들러 메서드에 @ResponseBody 애너테이션이 붙거나 핸들러 메서드의 리턴 값이 ResponseEntity일 경우, 내부적으로 HttpMessageConverter가 동작하게 되어 응답 객체(여기서는 DTO 클래스의 객체)를 JSON 형식으로 바꿔줍니다. 이를 JSON 직렬화(객체 -> JSON) 라고 합니다.

DTO 클래스와 엔티티 클래스의 역할 분리가 필요한 이유

계층별 관심사의 분리

​ DTO 클래스는 API 계층에서 요청 데이터를 전달받고, 응답 데이터를 전송하는 것이 주 목적인 반면에 Entity 클래스는 서비스 계층에서 데이터 액세스 계층과 연동하여 비즈니스 로직의 결과로 생성된 데이터를 다루는 것이 주목적입니다.

코드 구성의 단순화

​ DTO 클래스에서 사용하는 유효성 검사 애너테이션이 Entity 클래스에서 사용이 된다면 JPA에서 사용하는 애너테이션과 뒤섞인 상태가 되어 유지보수하기 상당히 어려운 코드가 됩니다.

REST API 스펙의 독립성 확보

​ 데이터 액세스 계층에서 전달받은 데이터로 채워진 Entity 클래스를 클라이언트의 응답으로 그대로 전달하게 되면 원치 않는 데이터까지 클라이언트에게 전송될 수 있습니다. DTO 클래스를 사용하면 회원의 로그인 패스워드 같은 정보를 클라이언트에게 노출하지 않고, 원하는 정보만 제공할 수 있습니다.

Custom Validator

​ 정규 표현식은 성능면에서 때로 비싼 비용을 치러야 할 가능성이 있습니다. 대신 Custom Validator를 구현하여 직접 애너테이션을 만들어서 유효성 검증을 적용할 수 있습니다.

Custom Annotation

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {NotSpaceValidator.class})
public @interface NotSpace {
    String message() default "공백이 아니어야 합니다";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Custom Validator

public class NotSpaceValidator implements ConstraintValidator<NotSpace, String> {

    @Override
    public void initialize(NotSpace constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value == null || StringUtils.hasText(value);
    }
}

기본적으로 CustomValidator를 구현하기 위해서는 ConstraintValidator 인터페이스를 구현해야 합니다. isValid 의 리턴값이 유효성 검증 결과입니다.

댓글남기기