0. 들어가며
이번 포스팅에서는 최근 경험했던 Spring Boot 에서의 Exception Class 설계와 Exception handling 에 대해 다뤄보고자 합니다. 처음에 Exception Class 를 만들어야 한다는 요구사항을 받았을 때 단순히 개발이 어렵기보다는 어떤 식으로 설계해야 좋은 어플리케이션이 될 수 있을지 고민을 많이 했었는데요, 그 문제해결의 과정을 공유드리도록 하겠습니다.
1. Exception Class 를 사용하는 이유
1-1. 표준 예외를 사용하라
Exception Class 를 정의해야 한다는 이야기를 듣고 가장 먼저 찾아본 자료는 Effective Java 였습니다. 사전에 정의되어 있는 예외를 사용하는 것이 아니라, 직접 예외 클래스를 정의해야하는 상황에서는 무엇을 고려하여 예외를 설계해야 하는지 감이 전혀 없었기 때문이었습니다. 실제로 Effective Java 는 아이템 69 ~ 75에 걸쳐 Java 의 예외처리에 대해 다루고 있었는데요, 그 내용중 한눈에 들어온 건 '표준예외를 사용하라' 였습니다.
💡 아이템 72. 표준 예외를 사용하라
- API 가 다른 사람에게 익히고 사용하기 쉬워진다
- 예외 클래스가 적을수록 메모리 사용량도 줄고 클래스를 적재하는 시간도 적게걸린다
1-2. 그럼에도 불구하고 사용자 정의 예외를 만들어야 한다면?
그래서 저는 아래와 같이 질문을 드렸습니다.
Q. Effective Java 에서는 웬만해선 Custom Exception 을 만들지 말고 Java 에서 기본적으로 제공하는 표준 예외를 사용하라고 하는데 굳이 예외 클래스를 따로 정의하는 이유가 있을까요? 😮
여러 이야기들을 해주셨던 것 같지만 가장 기억에 남는 장점은 모니터링의 용이성 👀 이었습니다. Sentry 같은 솔루션을 이용해 운영중인 어플케이션을 모니터링해보면 Exception Class 단위로 오류가 모니터링되는 걸 확인할 수 있는데요, 전체 시스템을 구성하는 어플리케이션이 많아지는 경우 CustomException Class 를 정의하는 게 오류 위치를 훨씬 직관적이고 빠르게 파악할 수 있다는 것이었습니다.
2. Exception Class 설계하기
이제 CustomException Class 를 사용하는 이유에 대해서는 알게 됐지만, 이런 필요로 인해 Effective Java 의 무시하고 너무 많은 CustomException class 를 만들어낼 수는 없었습니다. 그래서 저는 하나의 모듈별로 하나의 CustomException Class 를 정의했고, 자세한 오류 내용은 예외를 생성할 때 담아주고자 했습니다.
💡 CustomException Class 만들기
1. 어플리케이션에서 발생할 수 있는 오류 상황별 ErrorCode를 정의한 Enum 클래스를 만들어준다
2. RuntimeException 클래스의 하위 클래스로 CustomException 클래스를 만들어준다
2-1. CustomErrorCode Enum 클래스 생성하기
public enum CustomErrorCode {
// 원하는 오류코드와 내용을 정의합니다
NO_PRODUCT("ERR100", "등록되지 않은 상품입니다"),
ALREADY_EXIST_PRODUCT("ERR101", "이미 등록된 상품입니다"),
NO_USER("ERR200", "가입되지 않은 회원입니다"),
ALREADY_EXIST_USER("ERR201","이미 가입된 회원입니다")
UNKNOWN("ERR999", "서비스 이용에 불편을 드려 죄송합니다")
;
private final String errorCode;
private final String errorMessage;
CustomErrorCode(String errorCode, String errorMessage ) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public String getErrorCode() {
return errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
}
2-2. CustomException 클래스 생성하기
public abstract class CustomException extends RuntimeException {
private final String errorCode;
private final String errorMessage;
public CustomException (final CustomErrorCode customErrorCode) {
super(customErrorCode.getErrorMessage());
}
}
3. Exception Handler 로 예외 처리하기
이제 컨트롤러에 정의한 API에서 CustomException 이 발생하는 상황을 가정하고, 이 Exception 을 처리하기 위한 Handler 를 정의해보겠습니다. @ExceptionHandler 는 @Controller 또는 @RestController 가 적용된 Spring Bean 에서 발생하는 예외를 메서드에 정의한 방식대로 처리해주는 역할을 하고, 이번 예시에서는 @RestControllerAdvice 를 적용한 전역 예외처리기를 사용해보겠습니다. 이와 같은 방식을 사용하면 여러 컨트롤러에서 발생하는 다양한 예외를 AOP 방식으로 처리할 수 있습니다. 이제 아래와 같이 ExceptionHandler 를 정의한 뒤에 API 를 실행해보면 3-2 와 같은 결과를 확인할 수 있습니다.
💡 물론 @RestContoller 를 적용한 컨트롤러 내부의 메서드에 @ExceptionHandler 를 직접 적용함으로써 API 단위로 Exception 을 처리하는 것도 가능합니다
3-1. CustomExceptionHandler 클래스 생성하기
@RestControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(value = {CustomException.class})
@ResponseStatus(value = INTERNAL_SERVER_ERROR)
public ResponseEntity<?> customExceptionHandler(final CustomException customException) {
return new ResponseEntity<>(customException, INTERNAL_SERVER_ERROR);
}
}
3-2. 예외처리 테스트해보기
💡 CustomException Class 만들기
1. @RestController 어노테이션이 붙은 Controller 클래스에 테스트할 API 를 추가해준다
2. 해당 API 내부에서 CustomException 을 던지는 서비스(메서드)를 실행3. CustomExceptionHandler 가 ResponseEntity 에 담아 응답해준 CustomException 정보를 확인한다
4. 내용요약
- 이펙티브 자바는 되도록 표준 예외를 사용할 것을 권장한다
- 그럼에도 불구하고 사용자 정의 예외를 사용함으로써 모니터링 등 운영환경에서 얻을 수 있는 이점이 있다
- 이 때, 모듈별로 하나의 Exception Class 를 정의하고 실제 오류코드와 내용은 Enum 에 정의된 값을 이용하면 예외 클래스의 개수가 무분별하게 증가하는 것을 막을 수 있다
- @RestControllerAdvice 와 @ExceptionHandler 를 이용하면 AOP 방식의 전역 예외처리기를 만들 수 있다
댓글