Food id값을 통해서 DB의 Food를 가져오는 서비스 로직이다. 이때 id에 해당하는 Food가 존재하지 않으면 예외를 발생시키도록 하였다.
public FoodResponse get(Long foodId) {
final Food food = foodJpaRepository.findById(foodId).orElseThrow(()
-> new IllegalArgumentException(foodId + "id의 Food가 존재하지 않습니다.");
}
이 상태로 어플리케이션을 실행시키고 존재하지 않는 id의 Food를 조회하도록 요청하면 아래와 같이 에러가 발생한다. 별도로 try-catch처럼 예외가 발생했을 때 어떻게 처리해 줄지 구현하지 않았기 때문이다.
응답데이터로는 아래처럼 받을 수 있는데, 500 에러라고만 확인할 수 있고 어떤 부분에서 에러가 발생했는지 구체적으로 알기가 어렵다.
📌 예외처리하기
Spring어플리케이션에서 예외를 처리해 주기 위해서는 예외가 발생했을 때 어떻게 처리해 줄지를 구현해야 하는데, RestControllerAdvice를 이용해서 구현할 수 있다.
예외처리를 하기 위해 아래와 같은 4가지 클래스를 생성하였다.
- ErrorCode Enum은 구체적으로 어떤 예외인지, 또 그 예외는 어떤 상태코드를 가지는지를 나타내고 있다.
- ex) Food Not Found는 "찾을 수 없다"를 나타내주고 있기 때문에 404를 의미하는 HttpStatus.NOT_FOUND를 사용한다.
- 어플리케이션을 개발하면서 새롭게 발생시켜주고 싶은 에러가 있다면 아래에 추가해 주면 된다.
@Getter
public enum ErrorCode {
FOOD_NOT_FOUND(HttpStatus.NOT_FOUND, "[ERROR] Food Not Found");
private final HttpStatus status;
private final String message;
ErrorCode(final HttpStatus status, final String message) {
this.status = status;
this.message = message;
}
}
- 실제 런타임 때 IllegalArgumentException()처럼 에러를 발생시킬 수 있도록 별도의 커스텀 예외를 생성하였다.
- 위에서 생성한 ErrorCode를 필드로 가지고 있고, 서비스계층에서 로직을 처리할 때 발생시킬 수 있다.
@Getter
public class DeliveryApplicationException extends RuntimeException {
private final ErrorCode errorCode;
private final String message;
public DeliveryApplicationException(final ErrorCode errorCode, final String message) {
this.errorCode = errorCode;
this.message = message;
}
public HttpStatus getStatusCode() {
return errorCode.getStatus();
}
}
- 응답데이터에 전송할 양식을 작성하였다.
- 에러가 발생한 시각, 그리고 에러메세지, 세부메세지등으로 작성하였다. 필요에 따라 자유롭게 커스텀할 수 있을 것 같다.
@Getter
public class ErrorResponse {
private final LocalDateTime timestamp;
private final String message;
private final String details;
public ErrorResponse(final LocalDateTime timestamp, final String message, final String details) {
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
}
- 실제 Spring에서 에러를 핸들링하는 장소이다. try-catch의 try에서 에러가 발생했을 때 catch로 이동하여 처리해 주는 것과 같이, 어플리케이션에서 예외가 발생하면 @RestControllerAdvice의 위치로 이동된다.
- @ExceptionHandler를 통해 구체적으로 DeliveryApplicationException.class라는 예외가 발생하면 처리해 줄 메서드를 정의하고 있다. 마치 Controller가 요청 url에 따라 핸들러 역할을 해주는 것과 같다.
- 컨트롤러와 같이 메서드의 응답값으로 응답객체를 설정해 주며, 이때 원하는 에러메세지라던가 에러상태, 에러발생시각 등을 만들어서 전송해 주면 된다.
- 위에서 만들어준 ErrorResponse객체에 에러정보를 담아서 응답해 준다.
@RestControllerAdvice
public class GlobalControllerAdvice {
@ExceptionHandler(DeliveryApplicationException.class)
public ResponseEntity<ErrorResponse> applicationHandler(final DeliveryApplicationException e) {
return ResponseEntity
.status(e.getStatusCode())
.body(new ErrorResponse(LocalDateTime.now(), e.getErrorCode().getMessage(), e.getMessage()));
}
}
📌 커스텀 에러처리 결과
이제 실제 서비스 계층에서 아래처럼 예외를 던져줄 수 있다. 이때 @RestControllerAdvice의 핸들러로 이동하여 에러정보를 응답해 줄 것으로 예상할 수 있다.
public FoodResponse get(Long foodId) {
final Food food = foodJpaRepository.findById(foodId).orElseThrow(() ->
new DeliveryApplicationException(ErrorCode.FOOD_NOT_FOUND, String.format("id:%d Not Found", foodId)));
}
이제 예외가 발생해도 아래처럼 아무런 예외발생 로그가 뜨지 않는다.
실제 응답결과는 ErrorResponse클래스에 정의한 것과 같이 아래처럼 전송받을 수 있다.
프로젝트를 진행하면서 다양한 예외사항들을 잘 체크하고, 필요에 따라 다양하게 커스텀하면 좋을 것 같다.
'프로젝트 > 배달 REST API' 카테고리의 다른 글
[프로젝트] 주문상태 변경 시 데이터 동시성 문제 (1) | 2023.12.24 |
---|---|
[프로젝트] 비밀번호 암호화 적용하기 (0) | 2023.11.20 |