GlobalExceptionHandler만으로는 부족했던 이유!
처음 과제를 진행했을 때, 모든 예외를 하나의 GlobalExceptionHandler에서 처리하는 구조로 충분하다고 생각했습니다.
기존에 존재하는 IllegalArgumentException, EntityNotFoundException 등을 활용하면 공통적인 예외 상황을 무리 없이 처리할 수 있었기 때문입니다.
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<?> handleIllegalArgument(IllegalArgumentException e) {
return ResponseEntity.badRequest().body(
ResponseUtil.fail(e.getMessage())
);
}
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<?> handleEntityNotFound(EntityNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
ResponseUtil.error(404, e.getMessage())
);
}
이렇게 특정 예외마다 HTTP 상태 코드와 메시지를 정의하여 일관된 응답을 제공할 수 있었습니다.
하지만 게시글 수정/삭제 로직을 개발하면서 문제가 발생했습니다.
작성자가 아닌 사용자가 해당 게시글을 수정하거나 삭제하려고 할 때, 기존의 EntityNotFoundException이나 IllegalArgumentException을 사용하자 상황 설명이 애매해졌습니다.
예를 들어, 작성자가 아닌 사용자가 접근할 때 EntityNotFoundException을 던지면 클라이언트 입장에서는 "해당 게시글이 존재하지 않는구나"라고 오해할 수 있습니다.
실제로는 존재하지만 권한이 없는 상태임을 표현할 방법이 없어진 것입니다.
이런 맥락에서 처음엔 RuntimeException을 사용해봤지만,
이 역시 클라이언트가 받아들이기에 예외의 의도나 맥락이 명확하지 않았고,
GlobalExceptionHandler에선 어떤 상황인지 판단할 수 있는 근거도 부족했습니다.
그래서 결국 도입한 것이 바로 BusinessException이라는 추상 클래스 기반의 CustomException입니다.
public abstract class BusinessException extends RuntimeException {
private final HttpStatus status;
public BusinessException(HttpStatus status, String message) {
super(message);
this.status = status;
}
public HttpStatus getStatus() {
return status;
}
}
이 클래스는 모든 비즈니스 예외의 공통 부모로,
HttpStatus)message) 를 명확히 정의할 수 있도록 설계했습니다.그리고 이 클래스를 상속받아 ForbiddenException처럼 실제 상황에 맞는 구체적인 예외 클래스를 정의했습니다.
public class ForbiddenException extends BusinessException {
public ForbiddenException(String message) {
super(HttpStatus.FORBIDDEN, message);
}
}
이제 GlobalExceptionHandler에서는 기존의 Exception 처리 방식과 함께
이 BusinessException 계열의 예외도 따로 처리할 수 있게 되었습니다.
@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<?> handleForbidden(ForbiddenException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ResponseUtil.error(403, e.getMessage()));
}
앞으로는 UnauthorizedException, ConflictException, BadRequestException 등
도메인 상황에 맞는 예외들을 만들어 가독성 좋은 코드를 작성할 수 있게 되었습니다.
이번 경험을 통해, 단순히 "예외가 발생했다"는 신호 이상의 의미를 전달하는 것이 중요하다는 것을 깨달았습니다. 예외는 개발자와 클라이언트 모두에게 현재 상황을 자세히 설명하고 디버깅 및 유지보수, 그리고 사용자의 경험을 높이는 중요한 과정임을 알게 되었습니다.
GlobalExceptionHandler 하나로 시작했던 저의 예외처리 설계는,
이제는 CustomException을 통해 훨씬 더 명확하고 의미 있는 구조로 개발할 수 있을 것 같습니다!