@Valid 검증 예외 처리를 통한 공통 응답 보내기
현재 리드를 역임하고 있는 가천대학교 IT학술동아리 Leets의 5기 새 단장(?)을 맞아, 랜딩 페이지에 신기능을 도입하는 중이다.
신기능이라고 해봤자 거창한 것은 아니고, 모집 알림 신청 기능을 메일링을 통해 구현해주는 것이 목표이다!
이번 글은 해당 기능을 구현하면서 DTO에서 필드 검증 시에 발생하는 예외를 어떻게 처리하여 프런트에게 공통 응답을 보내줄 수 있었는지에 대한 내용이 될 것이다.
덕분에 오늘도 프런트랑 행복한 협업을 진행할 수 있게 되었다..>_<
DTO에서 필드 검증하기
DTO를 통해 필요한 값을 전달 받을 때, 프런트에서도 검증을 해주지만 혹시 모를 경우에 대비해 서버 단에서도 검증을 해주는 것이 좋다.
이를 위해서 우리는 @Valid 어노테이션을 사용해야 한다.
@Valid
@Valid 어노테이션은 다음과 같은 특징을 지닌다.
- 자바 표준 라이브러리에서 제공해주는 기본 어노테이션
- 주로 RequestBody(DTO)에서의 필드를 검증하는 데 사용
- 필드에 정의된 검증 어노테이션(@NotBlank, @Email 등)을 기반으로 검증
- 올바른 형식이 아닐 경우, MethodArgumentNotValidException 예외 발생
컨트롤러 단에 @Valid 어노테이션을 적용해줌으로써, DTO에 적용된 검증 어노테이션이 값에 대한 검증 로직을 올바르게 수행할 수 있다.
@Valid vs @Validated
그러면 @Validated 어노테이션과 @Valid는 무슨 차이를 지니고 있을까?
@Validated 어노테이션은 다음과 같은 특징을 지닌다.
- 스프링 프레임워크에서 제공해주는 어노테이션
- 필드뿐만 아니라, 그룹 단위로 검증을 진행하는데 사용
- 메서드 파라미터 및 반환값 검증 지원
- 올바른 형식이 아닐 경우, ConstraintViolationException 예외 발생
그룹 단위 검증을 비롯한 부가적인 기능이 @Validated 어노테이션에서 지원되지만, 해당 글에서 다루기에는 범위를 벗어나는 것 같아 보다 자세한 내용은 별도의 글로 작성하는 것이 좋을 것 같다는 생각이다.
@Valid와 @Validated 어노테이션의 간단한 차이점만 비교하고 넘어가도록 하자!
@RestControllerAdvice를 통한 검증 예외 처리하기
@Valid 어노테이션만 사용해 준다고 끝나는 것이 아니다.
만약 사용자가 잘못된 값을 입력했다면..? 그리고 그랬을 경우 오류 응답을 프런트한테 보내줘야 한다면..?
만약 검증 예외 처리를 진행해주지 않는다면, 다음과 같은 응답이 반환될 것이다.
해당 내용을 프런트에게 그대로 전달해 줄 수는 없는 노릇이다. 그래서 우리는 역시 항상 공통된 응답 객체를 만들어서 반환해주어야 한다.
이를 위해 우리는 @RestControllerAdvice를 통해 예외 처리 핸들러를 생성해주어야 한다.
커스텀 예외 처리를 @RestControllerAdvice를 통해 프런트에게 잘 전달하는 법
서버 로직을 개발하다 보면, 분명히 예외 처리를 해야 하는 순간이 다가오고는 한다.이때 우리는 대부분 커스텀 예외처리를 사용하고는 하는데, 필자가 작성한 코드를 참고하자면 다음과 같다.S
jwnnoh.tistory.com
이후, @ExceptionHandler를 통해 @Valid 어노테이션이 반환하는 ConstraintViolationException.class에 대한 예외를 받아 처리해 준다.
해당 공통 응답 객체명이 BindExceptionResponse인 이유는, ConstraintViolationException 클래스가 BindException 클래스를 확장한 클래스이기 때문이다. 그리고 BindException은 주로 @ModelAttribute 어노테이션을 사용하고, 컨트롤러 단에서 예외를 처리하고 싶을 때 사용된다고 한다.
해당 메서드가 전달받는 MethodArgumentNotValidException은 BindingResult 인터페이스를 내부적으로 구현하여 가지고 있다.
위에서 언급한 어마무시한 응답 내용들이 전부 BindingResult의 List<FleidError>에 포함되어 있는 것이다.
따라서 검증 오류 message를 추출하기 위해서, List<FleidError> 컬렉션에서 우리가 원하는 DefaultMessage를 추출하도록 구현해 주었다. 이후 해당 에러가 가지고 있는 기본 HTTP 상태 코드를 가져와 준 후, 우리가 방금 생성해 준 응답 객체에 해당 내용을 담아서 반환해 주도록 한다.
결과
이런 식으로 예외 처리가 가능해진다.
마무리
지금까지 서비스를 진행하면서 놀랍게도 한 번도 DTO 및 파라미터에 대한 검증 로직을 진행해 본 적이 없었는데, 이번 기회에 적용할 수 있게 되었다. 아무리 프런트엔드 단에서 예외 로직을 적용해 준다고 해도, 개발자 도구를 활용해 검증 로직은 언제든지 우회될 수 있기에 결국 서버 단에서도 검증 로직은 전부 구현해주어야 한다!
이번에는 간단히 필드 하나에 대한 검증 로직을 구현해 주었지만, 추후에는 보다 세부적이고 다양한 필드에 검증 로직을 적용해 볼 수 있기를 바라며 이번 글을 마치고자 한다.