🍃Spring

[Spring] Business Exception 처리하기

waveofmymind 2023. 2. 21. 14:11

개인 프로젝트를 진행 중 서비스 단에서 로직을 수행하다 예외가 터지면 지금은 Internel Server Error라던지, 클라이언트에게 보여줄 필요 없는메시지가 노출되었다.

 

비즈니스 레이어에서 발생하는 예외는 지금, BusinessException을 상속 받은 JsonWriteException, EntityNotFoundException과 같은 예외들인데, 이러한 예외들을 일일이 try-catch 처리하는 것 보다, 하나의 클래스에서 공통으로 처리하는 것이 유지보수에도 도움이 될꺼라고 생각해서 스프링 AOP를 활용하여 예외 처리를 적용해 보았다.

 

비즈니스 로직에서 예외를 처리하는 과정

  1. 클라이언트는 API를 이용하여 데이터를 보내면 컨트롤러에서 @RequestBody, @RequestParam 등으로 데이터를 받는다.
  2. 받은 데이터를 컨트롤러는 서비스와 같은 비즈니스 레이어로 넘긴다.
  3. 비즈니스 레이어에서 비즈니스 로직을 수행한다.
    1. 정상적으로 로직이 수행된 경우
      1. 로직에 대한 반환 값을 컨트롤러에 넘긴다.
      2. 컨트롤러는 받은 반환 값을 이용하여 응답 데이터를 구성하고 클라이언트에게 보낸다.
    2. 정상적으로 처리되지 않을 경우
      1. 비즈니스 로직을 수행하던 중, 예외가 발생했다.
      2. 이때 예외가 발생할만한 구간에 try-catch로 에러 처리를 수행했을 경우, throw new BusinessException()로 에러 처리를 던질 것이다.
      3. 이에 대해 @ExceptionHandler(BusinessException.class)가 붙은 메서드에서 이를 캐치해서 처리한다.
      4. 위 어노테이션이 붙은 메서드에서 응답 데이터를 구성하고, 이를 클라이언트에게 받는다.
      5. 클라이언트는 직접적인 오류 메시지를 안봐도 된다.

비즈니스 로직 예외 처리

자바 계층 구조 중 비즈니스 레이어에서 발생하는 에러를 잡아 오류를 띄우지 않고, 에러에 대한 응답 메시지를 만들어 클라이언트에게 전달해주는 기능

Presentation Layer - Business Layer - PersistenceLayer - Database Layer는 간단하게 생각하면

 

Controller - Service - Respository(DAO) - DB라고 생각하면 된다.

 

비즈니스 예외 처리는 이 중 비즈니스 레이어에서 발생하는 예외를 처리하는 핸들러에서 수행한다.

 

BusinessException

@Getter
public class BusinessException extends RuntimeException {
    private final ErrorCode errorCode;

    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }
}

비즈니스 예외는 런타임 예외를 상속받았다.

 

GlobalExceptionHandler

BusinessExceptionHandler로 만들어서 비즈니스 예외를 처리하는 핸들러만을 다룰 수 있지만, Global Exception도 처리하기 위해

GlobalExceptionHandler에서 비즈니스 예외도 처리할 것이다.

 

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(BusinessException.class)
    protected ResponseEntity<ResponseApiDto<?>> businessException(final BusinessException e) {
        log.error("Business Exception : " + e.getMessage());
        final ErrorCode errorCode = e.getErrorCode();
        return new ResponseEntity<>(ResponseApiDto.fail(errorCode.getStatus(),e.getMessage()), errorCode.getStatus());
    }
    ...
    
    }

활용

나는 서비스 로직에서 Id로 엔티티를 조회할때 엔티티가 없을 경우, EntityNotFoundException을 잡아서 처리하고 싶었다.

그래서 비즈니스 예외를 상속받은 EntityNotFoundExcpetion을 따로 만들었다.

EntityNotFountException.java

public class EntityNotFoundException extends BusinessException {
    public EntityNotFoundException() {
        super(ErrorCode.ENTITY_NOT_FOUND);
    }
}

이렇게 되면 @ExceptionHandler(BusinessException.java)에 잡히기 때문에

응답 메시지에 해당 에러 코드를 포함한 응답 데이터를 구성할 수 있다.

 

적용 전

{
    "status": 500,
    "message": "Internal Server Error",
    "data": null
}

적용 후

{
"status": 400,
"message": "Entity Not Found",
"data": null
}