[예외 처리] 비즈니스 로직에 대한 예외 처리
by mignon25체크 예외(Checked Exception) 와 언체크 예외(Unchecked Exception)
1. 체크 예외
- 발생한 예외를 잡아서(catch) 체크한 후에, 해당 예외를 복구하든지 회피하든지 등의 구체적인 처리를 반드시 해야 하는 예외
- Ex) ClassNotFoundException
2. 언체크 예외
- 예외를 잡아서(catch) 해당 예외에 대한 어떤 처리할 필요가 없는 예외
- RuntimeException을 상속한 예외
- Ex) NullPointerException, ArrayIndexOutOfBoundsException
개발자가 의도적으로 예외를 던지는(throw) 경우는 어떤 상황일까?
(Case 1) 백엔드 서버와 외부 시스템과의 연동에서 발생하는 예외 처리
<상황>
- 암호 화페 지갑과 연동하는 백엔드 서비스를 만든다고 가정
- 일반적으로 암호 화폐 지갑은 블록체인과 직접적으로 API 통신을 거치는 경우가 많지만, 백엔드 서버를 한 번 거쳐서 블록체인과 통신하는 경우도 있을 수 있다.
- A라는 사용자가 B라는 사용자에게 코인을 전송하기 위해 백엔드 서버가 블록체인과 API 통신하는 과정에서 블록체인으로부터 A 사용자의 코인 잔고가 부족하다는 메시지를 전달받고 프로세스가 중단되었다.
< 대처 >
- 이러한 예외 상황에서는 백엔드 서버 쪽에서 무언가 할 수 있는 것이 없다.
- 잔고 부족 상황을 클라이언트 쪽에 즉시 알려서 클라이언트가 지갑에 잔고를 채우는 것이 최선의 방법
=> 이런 경우, 백엔드 서버 쪽에서 예외를 의도적으로 던져서 클라이언트 쪽에 에러 발생 원인을 알려줄 수 있다.
(Case 2) 시스템 내부에서 조회하려는 리소스(자원, Resource)가 없는 경우
<상황>
- 커피 주문 애플리케이션 가정
- 회원 정보를 조회하기 위해 클라이언트 쪽에서 Controller의 getMember() 핸들러 메서드에 요청 전송
- DB를 조회하니 해당 회원 정보가 없음.
<대처>
=> 서비스 계층에서 해당 회원 정보가 없다는 예외를 의도적으로 전송해 클라이언트 쪽에 알릴 수 있다.
의도적인 예외 던지기/받기 (throw/catch)
< throw/catch 과정 >
- throw 키워드를 사용해 메서드 내에서 발생한 예외를 메서드 밖으로 던질 수 있다.
- 던지는 예외는 메서드 바깥, 즉 메서드를 호출한 지점으로 던져지게 된다.
- 서비스 계층에서 예외를 던지면 => API 계층인 Controller의 핸들러 메서드 쪽에서 잡아서 처리
< Controller에서의 예외 처리 >
- Controller에서 예외가 발생하면 Exception Advice에서 처리하도록 공통화 해둔 상태
- 서비스 계층에서 던진 예외 => 결과적으로 Exception Advice에서 처리하게 된다.
서비스 계층에서 예외(Exception) 던지기 (throw)
< throw - MemberService >
- throw 키워드를 사용해 RuntimeException 객체에 적절한 예외 메세지를 포함한 후 메서드 밖으로 던지기
@Service
public class MemberService {
...
...
public Member findMember(long memberId) {
// business logic
// (1)
throw new RuntimeException("Not found member");
}
...
...
}
< catch - GlobalExceptionAdvice >
- 서비스 계층에서 던져진 RuntimeException 을 GlobalExceptionAdvice 클래스에서 받기
@RestControllerAdvice
public class GlobalExceptionAdvice {
...
// 1
@ExceptionHandler
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleResourceNotFoundException(RuntimeException e) {
System.out.println(e.getMessage());
// 필요한 정보만 담은 에러 객체로 변환 처리 로직
return null;
}
}
사용자 정의 예외(Custom Exception) 사용
- 서비스 계층에서 의도적으로 던질 수 있는 예외 상황은 다양하다.
- 그러므로 RuntimeException 을 그대로 전달 받게 되면 어떤 상황에서 던져진 예외인지 구분이 어렵다.
=> RuntimeException 같은 추상적인 예외가 아닌, InSufficientBalanceException 같은 Custom Exception을 통해 해당 예외를 조금 더 구체적으로 표현해서 던질 수 있다.
1. 예외 코드 정의
public enum ExceptionCode {
MEMBER_NOT_FOUND(404, "Member Not Found");
@Getter
private int status;
@Getter
private String message;
ExceptionCode(int status, String message) {
this.status = status;
this.message = message;
}
}
- enum 을 정의하고, field를 정의하면, 생성자에서 정의한 대로 enum 의 파라미터 값이 적용된다.
- ExceptionCode를 enum 으로 정의하면 비즈니스 로직에서 발생하는 다양한 유형의 예외를 enum에 추가해서 사용할 수 있다.
2. BusinessLogicException 구현
- 서비스 계층에서 사용할 Custom Exception 정의 - BusinessLogicException
public class BusinessLogicException extends RuntimeException {
@Getter
private ExceptionCode exceptionCode;
public BusinessLogicException(ExceptionCode exceptionCode) {
super(exceptionCode.getMessage());
this.exceptionCode = exceptionCode;
}
}
- BusinessLogicException은 RuntimeException을 상속하고 있다.
- ExceptionCode를 멤버 변수로 지정하여 조금 더 구체적인 예외 정보들을 제공해줄 수 있다.
- enum 에서 지정한 status, message 등
- 그리고, 상위 클래스인 RuntimeException의 생성자 super로 예외 메세지 전달
- 내가 원하는 메세지로 예외 메세지가 설정된 예외 객체 생성
3. 서비스 계층에 BuisinessLogicException 적용
@Service
public class MemberService {
...
public Member findMember(long memberId) {
// TODO should business logic
// (1)
throw new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND);
}
...
}
4. Exception Advice 에서 BusinessLogicException 처리
@RestControllerAdvice
public class GlobalExceptionAdvice {
...
...
@ExceptionHandler
public ResponseEntity handleBusinessLogicException(BusinessLogicException e) {
System.out.println(e.getExceptionCode().getStatus());
System.out.println(e.getMessage());
// ErrorResponse 수정 로직
return new ResponseEntity<>(HttpStatus.valueOf(e.getExceptionCode().getStatus()));
}
}
< 변경된 부분 >
- 메서드 명
- 서비스 계층의 비즈니스 로직에서 발생하는 예외를 처리하는 것이 목적
- handleResourceNotFoundException -> handleBusinessLogicException 로 변경
- 메서드 파라미터 변경
- RuntimeException -> BusinessLogicException 을 전달 받는 것으로 변경
- @ResponseStatus(HttpStatus.NOT_FOUND) 제거
- @ResponseStatus 애너테이션은 고정된 HttpStatus를 지정한다.
- 따라서, BusinessLogicException 과 같이 다양한 Status를 동적으로 처리할 수 없다.
- ResponseEntity를 사용해서 HttpStatus를 동적으로 지정하도록 변경
Additional Keywords - Checked Exception / Unchecked Exception
- https://docs.oracle.com/javase/specs/jls/se7/html/jls-11.html
- https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
- https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/RuntimeException.html
- https://www.geeksforgeeks.org/checked-vs-unchecked-exceptions-in-java/
- https://rollbar.com/blog/how-to-handle-checked-unchecked-exceptions-in-java/
'Spring' 카테고리의 다른 글
| [Spring MVC] 서비스 계층 적용하기 (Prac Project - 3. 서비스 계층 <-> API 계층 연동) (0) | 2023.04.21 |
|---|---|
| [예외 처리] @RestControllerAdvice 사용한 예외 처리 (0) | 2023.04.20 |
| [예외 처리] @ExceptionHandler를 이용한 예외 처리 (0) | 2023.04.20 |
| DTO 유효성 검증 (Validation) - (1) (0) | 2023.04.18 |
| HTTP 요청/응답에서의 DTO(Data Transfer Object) (0) | 2023.04.17 |
블로그의 정보
Mignon'S Dev Log
mignon25