프로젝트 개발 단계에 들어가기 전에 응답에 대한 공통 양식을 설정하기 위해 개발된 팀 코드를 리뷰하기 위해서 해당 포스팅을 작성하였습니다.
정상/예외 응답 형식을 형식화 하기 위한 클래스입니다. 내부 코드는 다음과 같습니다.
@Getter
public class ApiResponse<T> {
@Nonnull
private final String code;
@Nonnull
private final String message;
private final T data;
private ApiResponse(@Nonnull String code, @Nonnull String message, T data){
this.code = code;
this.message = message;
this.data = data;
}
public static <T> ApiResponse<T> ok() {
return ok(null);
}
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<T>(HttpStatus.OK.name(), HttpStatus.OK.getReasonPhrase(), data);
}
public static <T> ApiResponse<T> error(ApiErrorType errorType) {
return error(errorType.name(), errorType.getMessage());
}
public static <T> ApiResponse<T> error(String code, String message) {
return new ApiResponse<T>(code, message, null);
code : 응답 type 이름message : 응답 메시지data : 응답 본문static ok() : 정상 응답을 위한 정적 메서드static error() : 예외 응답을 위한 정적 메서드정상 또는 예외 응답 시 정형화된 양식으로 응답하고자 위와 같이 정의되어 있습니다.
예외 응답의 경우, 발생하는 예외에 따라서 메시지나 코드를 다르게 응답해줘야 합니다. 이를 위해서 '예외 타입' 이라는 enum으로 관리를 해줍니다.
@Getter
public enum ApiErrorType {
BAD_REQUEST("잘못된 요청입니다."), // 400
UNAUTHENTICATED("인증이 실패하였습니다."), // 401
FORBIDDEN("권한이 없습니다."), // 403
INTERNAL_SERVER_ERROR("서버에서 에러가 발생하였습니다."); // 500
private final String message;
ApiErrorType(String message) {
this.message = message;
}
}
기본적으로 400,401,403,500 이라는 오류 응답에 대한 타입들을 정의한 코드입니다. 하지만, 실제로 이를 사용하기 위해서는 각 구현부에 try~catch를 사용하여 실제 발생한 예외를 잡고 ApiResponse에 error() 메서드를 통해 일정한 양식으로 예외 응답을 수행하도록 해줘야 합니다.
하지만, 각 응답하는 곳마다 해당 방식으로 처리하기에는 중복된 코드의 량이 많아집니다. 이를 일괄적으로 공통된 양식으로 처리하기 위해서 ControllerAdvice + ExceptionHandler 를 활용해주면 됩니다.
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 잘못된 요청 경우 예외 처리
*/
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<?> handler(BusinessException e) {
return ApiResponse.error(e.getCode(), e.getMessage());
}
...
}
RestControllerAdvice를 통해서 controller 전역에 처리가 가능하며, ExceptionHandler를 사용하여 예외를 핸들링 해줄 수 있습니다. 위 예시는 BusinessException 예외 발생 시, 해당 ExceptionHandler에서 catch하여 하위 메서드를 수행해줍니다.
메서드 내부에 ApiResponse 클래스의 error() 메서드를 활용하여 형식화된 응답처리가 가능합니다.
@Getter
public class BusinessException extends RuntimeException {
private final String code;
public BusinessException() {
super(ApiErrorType.BAD_REQUEST.getMessage());
this.code = ApiErrorType.BAD_REQUEST.name();
}
public BusinessException(final String message) {
super(message);
this.code = ApiErrorType.BAD_REQUEST.name();
}
public BusinessException(final String code, final String message) {
super(message);
this.code = code;
}
}
커스텀 예외 정의 시 RuntimeException을 상속받습니다. 생성자 식 내부에 ErrorType 내 정의한 메시지는 RuntimeException 생성자로 전달하며, code는 Exception class 내부에 정의하여 응답 시 ApiResponse 클래스에 전달해줍니다.
이와 같이 응답처리를 형식화하는 과정에 대해서 기록했습니다. 해당 내용들은 프로젝트 팀원분의 코드를 기반으로 작성되었으며, 정갈한 코드라고 생각되어 리뷰 겸 기록하였습니다. 위 과정을 통해서 공통 예외 처리 및 성공 처리에 대한 틀을 생각해볼 수 있었습니다.