커스텀 예외 처리를 @RestControllerAdvice를 통해 프런트에게 잘 전달하는 법
서버 로직을 개발하다 보면, 분명히 예외 처리를 해야 하는 순간이 다가오고는 한다.
이때 우리는 대부분 커스텀 예외처리를 사용하고는 하는데, 필자가 작성한 코드를 참고하자면 다음과 같다.
ServiceException.java
ExtendedServiceException.java
근데 왜 굳이 이렇게 귀찮게 별도로 커스텀 예외처리를 우리가 따로 명시해주어야 하는 걸까?
커스텀 예외 처리를 사용하는 이유
당연하게도, 스프링 부트는 기본적으로 예외가 발생하는 모든 부분에 대해 기본적인 예외 처리를 지원해 준다.
그리고 이런 예외가 발생하면 대부분의 백엔드 개발자들은 디버깅과 구글링을 통해 문제의 원인을 파악하고, 예외 발생 지점을 특정 짔고, 고친다. 우리가 백엔드 개발을 희망하여 혼자 프로젝트를 공부하는 거라면, 이는 전혀 문제가 되지 않는다.
하지만 우리는 프로젝트를 하면서 당연히 프론트엔드와 소통을 해야 하고, 서비스는 엔드 단에서 유저에게 원활하게 소통할 수 있어야 한다.
위와 같은 응답 JSON은 자세하기는 하지만, 정확히 어떤 사유로 예외가 발생하였는지 프론트엔드에서 알 수가 없다.
만약 사용자가 비밀번호를 잘못 입력해 로그인을 실패했다고 가정하자.
기본 예외 처리를 사용할 경우 프론트엔드 단에서는 공통 예외 응답(500, Internal Server Error)을 받게 될 것이고, 당연히 어떤 사유로 해당 예외를 받았는지 파악할 수 없을 것이다. 당연히 사용자 또한 마찬가지로 어떤 이유로 인해 로그인을 실패했는지 알 수 없을 것이고, 자연스레 사용자의 이탈률은 절정을 향해 달려갈 것이다..🥺
이를 방지하기 위해 백엔드 개발자는 프런트엔드 개발자와 원만한 합의를 통해 공통 응답/예외 처리 객체의 규격을 정해야 하고,
이를 정상적으로 전송해야 할 의무를 지니게 된다.
ErrorResponse.java
그런데 프론트엔드에서 계속 500 코드로만 응답이 온다고 하네요..
말만 들어도 정말 백엔드 개발자 입장에서는 소름이 끼치는 순간이다.. 분명 우리는 모든 경우에 대해 적절하게 예외 처리를 처리해 줬다고 생각했는데, 대체 왜 프론트엔드 단에서는 매번 같은 응답만 받는다고 하는 걸까?
이는 스프링이 우리가 지정해 준 커스텀 예외를 제대로 반환하지 못하기 때문이다. 따라서 우리의 의도대로 예외를 반환해 주기 위해 별도의 작업을 진행해줘야 하는데, 우리는 이를 @RestControllerAdvice 어노테이션을 통해 처리해 줄 수 있다. 🥹
@RestControllerAdvice
우선 @RestControllerAdvice를 언급하기 전에 @ControllerAdvice가 무엇인지 짚고 넘어가야 왜 우리가 @RestControllerAdvice를 사용해야 하는지 알 수 있다.
@ControllerAdvice
쉽게 말하자면 여러 개의 컨트롤러가 공유하는 공통된 로직을 @ControllerAdvice 어노테이션을 통해 처리할 수 있다는 내용이고, 우리는 예외 처리를 주로 다뤄볼 예정이다. 이게 어떻게 가능하냐면, 스프링은 전역으로 @ExceptionHandler 어노테이션을 @ControllerAdvice이 적용된 클래스에 사용할 수 있도록 해준다. 이를 통해 우리는 여러 개의 컨트롤러에서 발생하는 예외를 전역적으로 처리해줄 수 있게 된다.
다만 단점이 존재하는데, @ControllerAdvice는 예외를 처리할 때 HTML 뷰를 반환한다. 예외가 발생하면 우리가 별도로 지정해 준 뷰(엔드포인트)로 이동하게 되고, 만약 이를 지정해주지 않았을 경우 의도치 않은 결과를 맞이할 수 있는 것이다. 우리는 모든 응답을 JSON으로 반환하는 RESTful API를 구축하기에, 뷰를 스프링에서 책임지고 반환하는 방향은 우리가 원하는 방향이 아닐 것이다!
이를 방지하기 위해 우리는 @RestControllerAdvice를 사용한다.
@RestControllerAdvice
@ResponseBody 어노테이션을 보면 알 수 있듯이,@RestControllerAdvice는 @ControllerAdvice + JSON이라고 생각하면 된다.
따라서 @ExceptionHandler를 통해 예외를 처리할 때, 우리가 원하는 JSON 객체를 반환할 수 있다.
해당 어노테이션을 통해 우리는 모든 컨트롤러를 거치면서 발생하는 의도한 예외 처리를 상황에 맞는 내용을 담아서 프론트엔드에게 전달해 줄 수 있다.
아쉬웠던 부분
처음 작성하는 기술 관련 글인 만큼 부족한 내용도 많았고, 잘 다듬어진 글은 아니라고 생각한다.
해당 기술에 대해 자세히 알게 된 계기도 프로젝트를 진행하면서 프론트엔드와의 예외 처리 소통 관련해서 불편함을 겪어 이를 개선하면서이다. 지금 와서 생각해 보면 바쁘다고 미루지 말고 어떠한 부분을 기술적으로 개선/고쳤을 경우 바로 글로 남기는 것이 나에게도, 그리고 내 글을 읽어주시는 분들께도 더 좋지 않았을까라는 아쉬움이 든다.
그래도 해당 내용을 작성하면서 그 당시 어떤 방식으로 문제를 해결했는지 회고를 통해 다시 공부하게 된 것 같아 나름 만족스러운 부분도 존재한다.
마무리
서로와의 약속을 통해 통일된 응답을 반환해 주는 것은 소통, 그리고 프로젝트의 완성도에 있어서 매우 중요한 부분이다.
본인의 경우, 전역 예외 처리를 계속해서 @ControllerAdvice로 설정하는 까닭에 이상한 URL로 반환이 진행되어 프론트엔드에서 계속 CORS 오류가 발생한다고 하여 무척 애를 먹었던 기억이 난다.
이 글을 읽으시는 모든 분들께서는 @RestControllerAdvice로 전역 예외 처리를 설정해 행복한 RESTful API 개발을 하시길 바란다.🥹