Simple Handling Exception 라이브러리 : 왜 Exception Handling을 처리해야 하는가? -1-
개요
F-LAB에 소속된 멘토님의 리뷰를 받아가며, 한참 애플리케이션을 개발하던 중, 예외처리를 하는 것이 생각보다 간단하지 않다는 것을 알게 되었다. 물론 예외 메세지를 api에 전달하는 것 자체는 JSR-303 validation api의 도움을 받는다면, 매우 간단하다. 특히, 뷰 렌더링을 서버에서 처리하는 SSR 방식의 개발은, BindingResult
와 같이 사용하여, Validator
를 좀더 다채롭게 사용할 수 있다. 하지만, HTTP api로 개발을 해야하는 경우, @ModelAttribute
와 달리 메세지 컨버터로 메세지를 리턴해야할 경우, 검증 위반 필드 목록을 불러오려면, 인위적으로 코드를 건들여야 했고, 그러다 보면, 불필요하다 싶을 정도로 반복되는 코드가 발생하는 경우가 많았다. 특히, FieldError
및 ObjectError
를 Collection
의 형태로 가져와야 할 때, 별 다른 조치를 취하지 않으면, 10줄이 넘는 코드를 무의미하게 반복하게 되는 경우가 발생했다.
그래서 이번에, validation 및 exception handling을 하는 과정에서 발생한 '나쁜냄새'의 코드를 개선하여, 핵심로직으로부터 최대한 분리하는 과정을 공유하고자 한다.
INDEX
- Spring Boot의 Bean Validation
- 왜
Validation
이 필요한가? - Validator
- Bean Validator
- 왜
@ModelAttribute
&@RequestBody
in Bean Validation- Exception handling in
HttpMessageConverter
Spring Boot의 Bean Validation
Validation
은 직접 구현할 수도 있지만, 하이버네이트에서 제공하는 Validation
애노테이션을 사용하는 방법도 있어서, 필요에 따라 다양한 방식을 활용해볼 수 있다. 하지만 다양하게 활용하기 위해서는 용도와 목적을 명확히 아는 것이 중요하다. 그래서 사용하기에 앞서 Validation의 원리, 그리고 왜 필요한지를 먼저 정리해볼 필요가 있었다.
왜 Validation
이 필요한가?
제목 그대로 스스로 반문해보자. 왜 Validation
이 필요할까? 이유는 간단하다. 제공할 수 있는 서비스 규격에 맞게 데이터를 검증할 과정이 필요하기 때문이다. 그렇다면, 왜 Validation
을 서버단에서 처리하는 기능을 주는가? 서버는 그냥 서비스에 필요한 데이터를 제공하고, 데이터에 대한 검증은 클라이언트에 해도 상관 없지 않을까? 어떻게보면, 성능면에서는 클라이언트에서 검증을 하는 것이 더 리소스를 절약할 수 있지 않을까?
결론부터 이야기 하자면, *"둘다 필요하다."*라고 할 수 있다. 클라이언트나, 서버에서 검증을 했을 때, 얻는 트레이드 오프를 정리해보면 다음과 같다.
클라이언트에서 검증 할 경우 | 서버에서 검증을 할 경우 |
---|---|
검증 조작 가능성이 높다. | 검증 조작 가능성이 낮다. |
즉각적인 사용성이 높다.(UX) | 사용성이 다소 낮다. |
정리해보면, 클라이언트 단의 검증은 보안성이 낮지만, 사용자에게 UX를 제공해줄 수 있고, 서버 단의 검증은 보안성이 높지만, 사용자에게는 다소 불친절할 수 있다는 것이다. 즉, 어떻게 보면, 서로 보완하는 특성을 가지고 있기 때문에, 둘의 장점을 잘 활용하여, 서버와 클라이언트단에서 같이 검증을 하는 것이 안전하면서, 효율적일 듯 하다.
Validator
Validator
는 java 진영에서 제공하는 검증관련 인터페이스 api 이다. supports()
를 구현하여, 검증이 가능한 타입의 모델인지 확인하고, validate()
를 구현한다. validate()
를 구현하는 과정에서 검증에 위반한 필드 정보와 메세지 내용을 Errors
에 담는다. Errors
에 담긴 검증 위반 필드 정보는 일반적으로 @Controller
빈의 핸들러 메소드 파라미터에 BindingResult
를 통해 꺼내볼 수 있다. Validator
의 인터페이스 구조는 다음과 같다.
public interface Validator {
boolean supports(Class<?> clazz); // 해당 검증기의 지원여부 확인
void validate(Object target, Errors errors); // 검증 대상 객체, BindingResult
}
Validator
에 대한 자세한 내용과 작동 매커니즘은 공식문서를 참고하도록 하자.
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#validation
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-config-validation
Bean Validator
위의 공식문서를 읽어보았거나, Validator
를 직접 구현하면서 개발을 해온 사람이라면, Validator
를 직접 구현하고, 사용하는 것은 귀찮고 번거롭다. 결국엔 데이터 바인딩 과정에서 공백이 있어선 안된다.
, null이선 안된다.
, 특정 범위를 넘어선 안된다.
와 같이 다루는 필드만 다르지, 검증 로직은 솔직히 크게 다르지 않다. 다행이도 이러한 반복되는 작업을 스프링에선 @Validated
와 @Valid
와 같은 검증 애노테이션을 통해 간단하게 사용할 수 있는 방법을 제공한다. 사용법과 컨셉은 이미 공식 문서와 스프링 부트관련 블로그에서 잘 설명하고 있으므로, 자세한 내용은 아래 링크를 제공하는 것으로 대신하겠다.
https://www.baeldung.com/spring-boot-bean-validation
@ModelAttribute
& @RequestBody
in Bean Validation
둘의 차이점을 '용도'에서 살펴보자면, @ModelAttribute
는 Http 요청 파라미터와 모델간의 데이터 바인딩을 Model과 DataBinder
을 활용하는 'Controller method argument'이고, @RequestBody
는 HttpMessageConverter
를 활용하여, HTTP Request Body에 접근하도록 도와주는 'Controller method argument' 주로, Rest api 방식의 Http api를 개발할 때 사용하다. 좀 더 자세한 내용은 아래, 공식 문서를 보자.
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-methods
Exception handling in HttpMessageConverter
둘의 차이점은 @ModelAttribute
는 파라미터 단위로 데이터를 바인딩하고, @RequestBody
는 객체 단위로 데이터를 바인딩한다. 이 차이점은 전자는 필드 단위로 바인딩하기 때문에 일부 필드가 바인딩이 실패해도 나머지 필드가 성공한다면, Validation
을 진행하거나, @Controller
를 거쳐, 예외 정보를 담은 응답 메세지를 리턴하지만, 후자의 경우, 하나의 필드라도 바인딩이 실패하면, 바로 예외가 발생하여, 컨트롤러를 호출하지 못하고, 또한, Validator
를 적용하지 못한다. 따라서, HttpMessageConverter
로 응답 메세지를 리턴해야할 경우, 데이터 파싱을 애초에 제대로 하는 것이 가장 중요하지만, 데이터 파싱이 잘못되었을 경우에 대핸 예외를 핸들링하여, 에러 메세지로 응답할 수 있도록 해야한다.
마무리
지금까지, Validator
와 Bean Validator
를 간단히 알아보고, @ModelAttribute
& @RequestBody
를 통해 validation
을 진행할 때의 차이점을 짚어보며, 왜 HttpMessageConverter
의 경우에 Exception Handling
을 따로 처리해야하는 이유에 대해 알아보았다. 다음에는 Exception Handling
을 처리하는 과정과 결과물을 라이브러리로 배포한 과정에 대해 이야기를 해보도록 하겠다.