Spring - Validation , Data Binding

CYSSSSSSSSS·2024년 4월 26일
0

스프링

목록 보기
16/16

Spring

Validation, Data binding

Validation?

  • 사용자 혹은 다른 서버로부터의 요청 내용에서 잘못된 내용이 있는지 확인해보는 단계를 나타낸다.

Validation 종류

데이터 검증

  • 필수 데이터 존재 유무
  • 문자열 길이나 숫자형 데이터 값의 범위
  • 이메일/신용카드 형식이 맞는지

비즈니스 검증

  • 서비스 정책에 따라 데이터를 확인하여 검증
  • 예)배달앱인 경우 배달 요청을 할때 해당 주문건이 결제 완료 상태인지 확인
  • 예) 사용자가 금액을 보내야 할때 보내는 금액이 계좌에 남아있는 금액보다 작은지 여부
  • 경우에 따라 외부 API를 호출하거나 DB의 데이터까지 조회하여 검증하는 경우도 존재

Spring의 Validation

  • 스프링은 웹 레이어의 종속적이지 않은 방법으로 Validation을 하기위해 의도하고 있다. 주로 두가지의 validation으로 진행한다.

Java Bean Validation

  • JavaBean 기반으로 간편하게 개별 데이터를 검증
  • JavaBean 내에 어노테이션으로 검증방법을 명시한다.
public class MemberCreationRequest {
		@NotBlank(message="이름을 입력해주세요.")
		@Size(max=64, message="이름의 최대 길이는 64자 입니다.")
    private String name;
		@Min(0, "나이는 0보다 커야 합니다.")
    private int age;
		@Email("이메일 형식이 잘못되었습니다.")
    private int email;

    // the usual getters and setters...
}
  • 검증 방식에 따라 각각의 다른 어노테이션을 적용한다.
  • NotBlank(빈칸 금지),Size(크기) ,Min(최소) , Email(이메일형식)
  • 어노테이션을 붙인다고 단순하게 바로 검증이 되는것은 아니다.
@PostMapping(value = "/member")
public MemeberCreationResponse createMember(
	@Valid @RequestBody final MemeberCreationRequest memeberCreationRequest) {
	// member creation logics here...
}
  • 작성한 어노테이션을 @Valid와 @RequestBody를 이용하여 적용시키고 문제가 없을때 정상적으로 서비스를 실행 시킨다.
  • 검증중 실패가 발생하면 MethodNotArgumentNotValidException이 발생한다.

Spring Validator를 인터페이스 구현을 통한 Validation

public class Person {
    private String name;
    private int age;
    // the usual getters and setters...
}
  • 위처럼 Person이라는 클래스에 name과 age라는 javaBean 객체가 있을때
    이를 활용하여 Implements를 시킨다면
public class PersonValidator implements Validator {
    /**
     * This Validator validates only Person instances
     */
    public boolean supports(Class clazz) {
        return Person.class.equals(clazz);
    }
    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
        Person p = (Person) obj;
        if (p.getAge() < 0) {
            e.rejectValue("age", "negativevalue");
        } else if (p.getAge() > 110) {
            e.rejectValue("age", "too.darn.old");
        }
    }
}
  • supports: Validator가 동작할 조건을 정의,주로 class타입을 비교
  • validate : 원하는 검증을 진행한다.

Validation 주의 사항

  • Validation이 여러 군데 흩어져 있으면 테스트 및 유지보수성이 떨어진다.
  • 중복된 검증 : 정책 변경시에 모든 중복 코드를 수정해야 한다.
  • 다른 검증: 여러 군대에서 다른 정책을 따르는 검증이 수행될 수 있음
  • 가능한 Validation은 로직 초기에 수행 후 실패 시에는 Exception을 던지는 편이 처리가 간편하다.

실무 활용 패턴

  • Validation을 만들때 계속해서 반복적인 Validation이 나오는 경우가 많다.
  • 정책이 바뀔때 중복된 코드를 전부 수정해야하는 경우가 발생한다.
  • 가장 이상적인 Validation은 요청을 받는 DTO에서 JavaBean Validation을 이용해서 단순한 데이터의 유무나 범위나 형식을 1차로 검증을 수행한다.(1차)
  • 실제 서비스 로직 초반에는 비즈니스 검증을 시행하게 된다.
  • 정상 상태, 처리에 관한 조건이 완성이되어 있는지 확인해보고 실패할 경우 Custom Exception에 Error Message를 저장하고 던지는 방식으로 예외처리를 하고 Exception Handler를 통해 예외 처리를하고 에러 응답을 생성한다.

Spring Validator의 장단점

  • JavaBean에 비해 복잡한 검증이 가능하다(장점)
  • Validation을 수행하는 코드를 찾기 어렵다(단점)
  • Validation 하는 곳을 찾기 어렵다.
  • 중복된 검증이나 다른 검증을 하게된 경우가 많다.

Data binding

  • 사용자나 외부 서버의 요청 데이터를 특정 도메인 객체에 저장하여 우리 프로그램 Request에 담아주는 것을 뜻한다.
  • 쉽게 설명하면 "안녕하세요 [이름]"을 사용자에게 보여주는 기능을 만들어본다고 가정할 때 개발자는 사용자가 입력한 이름을 직접 가져와서 화면에 띄워줘야 한다. 하지만 데이터 바인딩을 이용하면 "이름"을 입력하는 필드를 직접 연결하여 사용할 수 있다. 이게 데이터 바인딩이다.

Convert<S,T> Interface

  • S(Source)라는 타입을 받아서 T(Target)이라는 타입을 변환해주는 interface이다.
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
    T convert(S source);
}
// 요청
GET /user-info
x-auth-user : {"id":123, "name":"Paul"}
// 유저 객체
public class XAuthUser {
    private int id;
    private String name;
    // the usual getters and setters...
}
@GetMapping("/user-info")
public UserInfoResponse getUserInfo(
	@RequestHeader("x-auth-user") XAuthUser xAuthUser){
	// get User Info logic here...
}
  • 파라미터에 json형식이 문자열이 담겨오는 경우 해당 문자열을 특정 Dto에 담고 싶은 경우
  • json 형식의 문자열을 XAuthUser객체에 바로 담고 싶은 경우 ConverterBean으로 등록하면 된다.
@Component
public class XAuthUserConverter implements Converter<String, XAuthUser> {
	@Override
	public XAuthUser convert(String source) {
		return objectMapper.readValue(source, XAuthUser.class);
	}
}
  • 즉 Json 형식으로 받은 데이터는 문자열이기 때문에 이를 형식에 맞게 바꾸기 위해서 Converter가 필요하다.
  • Converter<String , XAuthUser>는 String부분은 바뀌기전 데이터 형식
    Json 문자열 형식을 변환하기 때문에 String 자료형을 명시하고 이후에 XAuthUser는 내가 데이터를 변환하면 어떤 클래스 형식의 멤버변수로 변경 할 것인지를 명시하는 것이다.

Formatter

  • 특정 객체간 String간의 변환을 담당
  • print: API요청에 대한 응답을 줄 때 , Date 형식으로 된 데이터를 특정 locale에 맞춘 String으로 변환
  • parse: API 요청을 받아올 때,특정 locale의 데이터를 다시 원래 자료형으로 변환
package org.springframework.format.datetime;

public final class DateFormatter implements Formatter<Date> {
    public String print(Date date, Locale locale) {
        return getDateFormat(locale).format(date);
    }

    public Date parse(String formatted, Locale locale) throws ParseException {
        return getDateFormat(locale).parse(formatted);
    }
		// getDateFormat 등 일부 구현은 핵심에 집중하기 위해 생략... 
}
profile
개발자 되고 싶어요

0개의 댓글