Simple Handling Exception 라이브러리 : 왜 Exception Handling을 처리해야 하는가? -1-

2021. 11. 1. 12:15작업일지

개요

F-LAB에 소속된 멘토님의 리뷰를 받아가며, 한참 애플리케이션을 개발하던 중, 예외처리를 하는 것이 생각보다 간단하지 않다는 것을 알게 되었다. 물론 예외 메세지를 api에 전달하는 것 자체는 JSR-303 validation api의 도움을 받는다면, 매우 간단하다. 특히, 뷰 렌더링을 서버에서 처리하는 SSR 방식의 개발은, BindingResult와 같이 사용하여, Validator를 좀더 다채롭게 사용할 수 있다. 하지만, HTTP api로 개발을 해야하는 경우, @ModelAttribute와 달리 메세지 컨버터로 메세지를 리턴해야할 경우, 검증 위반 필드 목록을 불러오려면, 인위적으로 코드를 건들여야 했고, 그러다 보면, 불필요하다 싶을 정도로 반복되는 코드가 발생하는 경우가 많았다. 특히, FieldErrorObjectErrorCollection의 형태로 가져와야 할 때, 별 다른 조치를 취하지 않으면, 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://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.validation

https://www.baeldung.com/spring-boot-bean-validation

@ModelAttribute & @RequestBody in Bean Validation

둘의 차이점을 '용도'에서 살펴보자면, @ModelAttribute는 Http 요청 파라미터와 모델간의 데이터 바인딩을 ModelDataBinder을 활용하는 'Controller method argument'이고, @RequestBodyHttpMessageConverter를 활용하여, 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로 응답 메세지를 리턴해야할 경우, 데이터 파싱을 애초에 제대로 하는 것이 가장 중요하지만, 데이터 파싱이 잘못되었을 경우에 대핸 예외를 핸들링하여, 에러 메세지로 응답할 수 있도록 해야한다.

마무리

지금까지, ValidatorBean Validator를 간단히 알아보고, @ModelAttribute & @RequestBody를 통해 validation을 진행할 때의 차이점을 짚어보며, 왜 HttpMessageConverter의 경우에 Exception Handling을 따로 처리해야하는 이유에 대해 알아보았다. 다음에는 Exception Handling을 처리하는 과정과 결과물을 라이브러리로 배포한 과정에 대해 이야기를 해보도록 하겠다.