Java/Spring & Spring Boot

DTO (Data Transfer Object) 과 Validation

마손리 2023. 4. 12. 22:45

DTO (Data Transfer Object)

DTO는 Data Transfer Object의 약자로 마틴 파울러(Martin Fowler)가 ‘Patterns of Enterprise Application Architecture’ 라는 책에서 처음 소개한 엔터프라이즈 애플리케이션 아키텍처 패턴의 하나이다.

 

이름에서 알수있듯이 데이터의 전송을 위한 '객체'이다.

 

클라이언트에서 요청을 통해 서버로 JSON 포멧의 데이터를 보내면 DispatcherSerblet의 HandlerAdapter가 받은 JSON 데이터를 지정된 DTO객체로 만들어주게된다. 또한 응답시에도 DTO(객체)를 응답해주게되면 HandlerAdapter는 JSON 포멧으로 데이터를 변환시킨 뒤 클라이언트로 응답을 보내게된다. 

 

이렇게 DTO는 클라이언트와 서버간 데이터전송을 위해 사용되며 비즈니스 로직을 갖고 있지 않는 순수한 데이터 객체getter와 setter(혹은 생성자), 직렬화, 역직렬화 로직만을 가져야한다.

 

Spring MVC에서는 DTO만 만들어주면 HandlerAdapter에서 직렬화와 역직렬화를 해준다.

 

  • 직렬화: 객체를 특정한 포멧으로 변환하는 작업(ex. 객체 => JSON)
  • 역직렬화: 특정한 포멧의 데이터를 객체로 변환하는 작업(ex. JSON => 객체)

 

이때 DTO는 단순히 데이터의 전송을 위해 데이터 구조를 변환시키는 것뿐만 아니라 받은 데이터가 필요한 데이터인지 혹은 알맞은 데이터인지를 확인하는 유효성(Validation)검증을 진행할수 있다.

 

유효성(Validation)검증

유효성 검증은 클라이언트에게서 요청과 함께 보낸 데이터가 올바른 정보인지 확인하는 단계이다. 

예를 들어 어떠한 비즈니스 로직에 필수인 email이란 데이터가 들어있는지, 들어있다면 올바른 email형식인지에 대해 검사를 진행한뒤 모든 데이터가 올바르다면 DTO객체로 생성이 된다.

 

Spring MVC에서는 DTO를 위한 클래스를 생성하면 에너테이션을 이용해 유효성을 검사할 수 있다.

 

DTO 및 Validation 예시)

public class MemberPostDto {
    @NotBlank		// Validation Annotations
    @Email
    private String email;
    @NotBlank(message = "이름을 입력해주세요")
    @OnlyEng	// 영어만 가능하도록 사용자정의 애너테이션 사용
    private String name;
    @Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
    message = "폰넘버 예시 010-1234-1234")
    private String phone;
    
    //DTO는 getter/setter(혹은 생성자)와 직렬화 역직렬화 메서드만을 가진다.
    //Spring MVC에서는 직렬화 역직렬화를 대신해주므로 생략
    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 phone; }
    public void setPhone(String phone) { this.phone = phone; }
}

위와 같이 @Pattern정규표현식을 이용하여 직접 validation을 작성해줄 수도 있지만 만약 validation이 복잡하거나 자주사용된다면 직접 애너테이션을 만들어 사용해 줄 수도 있다. 위의 코드에서 @OnlyEng사용자가 직접 정의한 validation annotation이다.

 

 

사용자정의 유효성 검사 예시)

OnlyEng 인터페이스

@Target(ElementType.FIELD)  // 해당 애너테이션을 적용할 대상 (멤버변수에 사용할 것이므로 FIELD)
@Retention(RetentionPolicy.RUNTIME) // 해당 에너테이션의 지속 시간 (클래스파일이 실행시 이용가능하도록 RUNTIME)
@Constraint(validatedBy = {OnlyEngValidator.class}) // validation 로직 클래스
public @interface OnlyEng { // @interface를 이용하여 애너테이션 정의
    String message() default "영어만 입력되어야 합니다."; // 유형성 검증 실패시 표시되는 디폴트 메세지
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

사용자 정의 애너테이션 생성을 위한 인터페이스이며 @Constraintvalidation을 수행할 클래스를 참조해준다.

 

OnlyEngValidator 클래스

validation을 직접 작성하기 위해서는 ConstraintValidator 인터페이스를 구현해야 한다.

public class OnlyEngValidator implements ConstraintValidator<OnlyEng, String> {
    // 사용자정의 validation을 작성하기 위해서는 ConstraintValidator 인터페이스를 구현해야함
    // OnlyEng은 현재 validation과 매핑된 Annotation, String은 해당 Annotation으로 검증할 대상 변수의 타입
    @Override
    public void initialize(OnlyEng constraintAnnotation) {//@OnlyEng 애너테이션으로 해당 validation을 실행
        ConstraintValidator.super.initialize(constraintAnnotation);
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value == null || value.matches("^([a-zA-Z]+\\s)*[a-zA-Z]+$");
    }// validation 로직 작성, boolean으로 false인 데이터는 유효성 검사에 실패하며 예외발생
}

 

(더 많은 정규 유효성 검사 애너테이션 : https://hyeran-story.tistory.com/81 )

 

 

 

Validation 사용을 위한 controller 정의

@RestController
@RequestMapping("/v1/members")
@Validated
public class MemberController {
    @PatchMapping("/{memberId}")
    public ResponseEntity<MemberPatchDto> editMember(@PathVariable("memberId") @Min(1) long memberId, 
                                                     @Valid @RequestBody MemberPatchDto memberPatchDto){
        return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
    }
}

 

 

@PathVariable에 validation을 사용해 주기 위해서는 해당 클래스@Validated라는 애너테이션을 추가해주고 @PathVariable가 사용되는 매개변수에 validation(ex. @Min)을 사용한다. 

 

Query parameter인 @RequestParam도 동일하다.

 

위의 예시들처럼 DTO에서 유효성 검사를 진행할경우 @RequestBody 매개변수에 @Valid 애너테이션을 사용하면 validation이 적용된다.