Skip to content

응답 표준화 및 에러 처리 방법 정의

LeeJongSeon edited this page Oct 28, 2024 · 1 revision

개요

애플리케이션의 예외 처리와 응답 형식을 표준화하여 개발 효율성과 코드 일관성을 높이고자 합니다. 이를 위해 다음과 같은 클래스를 구현했습니다:

  • BaseResponse<T>: 모든 API 응답의 기본 형태를 정의하는 클래스
  • BusinessException: 비즈니스 로직에서 발생하는 예외를 처리하기 위한 사용자 정의 예외 클래스
  • GlobalExceptionHandler: 전역 예외 처리를 담당하는 클래스
  • ErrorCode 열거형(enum): 애플리케이션에서 사용되는 에러 코드를 관리하는 열거형

BaseResponse 클래스

BaseResponse<T> 클래스는 모든 API 응답의 기본 구조를 정의합니다.

클래스 구조

@Getter
@NoArgsConstructor
public class BaseResponse<T> {
    private boolean success;
    private T data;
    private Error error;

    // 성공 응답 생성자
    public BaseResponse(T data) {
        this.success = true;
        this.data = data;
        this.error = null;
    }

    // 실패 응답 생성자
    public BaseResponse(ErrorCode errorCode) {
        this.success = false;
        this.data = null;
        this.error = new Error(errorCode);
    }

    @Getter
    @NoArgsConstructor
    private static class Error {
        private String code;
        private String message;

        Error(ErrorCode errorCode) {
            this.code = errorCode.getCode();
            this.message = errorCode.getMessage();
        }
    }
}

설명

  • success: 요청의 성공 여부를 나타냅니다.
  • data: 요청이 성공한 경우 반환되는 데이터입니다.
  • error: 요청이 실패한 경우 에러 정보를 담고 있습니다.

사용 방법

성공 응답 생성

BaseResponse<UserDto> response = new BaseResponse<>(userDto);

실패 응답 생성

BaseResponse<Object> response = new BaseResponse<>(ErrorCode.USER_NOT_FOUND);

BusinessException 클래스

비즈니스 로직에서 발생할 수 있는 예외를 처리하기 위한 사용자 정의 예외 클래스입니다.

클래스 구조

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

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

설명

  • errorCode: 발생한 예외에 대한 에러 코드입니다.
  • 생성자에서는 errorCode를 받아 부모 클래스인 RuntimeException에 메시지를 전달합니다.

사용 방법

서비스나 비즈니스 로직에서 특정 조건에 따라 예외를 발생시킬 수 있습니다.

if (user == null) {
    throw new BusinessException(ErrorCode.USER_NOT_FOUND);
}

GlobalExceptionHandler 클래스

전역적으로 예외를 처리하여 일관된 응답을 제공하는 클래스입니다.

클래스 구조

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    protected ResponseEntity<BaseResponse<Object>> handleBusinessException(BusinessException e) {
        log.error("Business Exception: {}", e.getMessage());
        return ResponseEntity
                .badRequest()
                .body(new BaseResponse<>(e.getErrorCode()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity<BaseResponse<Object>> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("Validation Exception: {}", e.getMessage());
        return ResponseEntity
                .badRequest()
                .body(new BaseResponse<>(ErrorCode.INVALID_INPUT_VALUE));
    }

    @ExceptionHandler(Exception.class)
    protected ResponseEntity<BaseResponse<Object>> handleException(Exception e) {
        log.error("Internal Server Error: {}", e.getMessage());
        return ResponseEntity
                .internalServerError()
                .body(new BaseResponse<>(ErrorCode.INTERNAL_SERVER_ERROR));
    }
}

설명

  • @RestControllerAdvice: 전역 예외 처리를 위한 어노테이션입니다.
  • @ExceptionHandler: 특정 예외를 처리하는 메서드를 지정합니다.

처리되는 예외

  1. BusinessException

    • 비즈니스 로직에서 발생하는 예외를 처리합니다.
    • HTTP 상태 코드 400(Bad Request)을 반환합니다.
  2. MethodArgumentNotValidException

    • 요청 데이터의 유효성 검증 실패 시 발생하는 예외를 처리합니다.
    • HTTP 상태 코드 400(Bad Request)을 반환합니다.
  3. Exception

    • 그 외의 모든 예외를 처리합니다.
    • HTTP 상태 코드 500(Internal Server Error)을 반환합니다.

ErrorCode 열거형

애플리케이션에서 발생할 수 있는 에러 코드를 관리하는 열거형입니다.

열거형 구조

@Getter
public enum ErrorCode {
    // 공통 에러
    INVALID_INPUT_VALUE("C001", "Invalid input value"),
    INTERNAL_SERVER_ERROR("C002", "Internal server error"),

    // 사용자 도메인 에러
    USER_NOT_FOUND("U001", "User not found"),
    DUPLICATE_EMAIL("U002", "Email already exists"),

    // 바이크 도메인 에러
    BIKE_NOT_FOUND("B001", "Bike not found"),
    BIKE_ALREADY_IN_USE("B002", "Bike is already in use"),

    // 대여 도메인 에러
    RENTAL_NOT_FOUND("R001", "Rental not found"),
    INVALID_RENTAL_STATUS("R002", "Invalid rental status"),

    // 결제 도메인 에러
    PAYMENT_FAILED("P001", "Payment processing failed"),
    INSUFFICIENT_BALANCE("P002", "Insufficient balance");

    private final String code;
    private final String message;

    ErrorCode(String code, String message) {
        this.code = code;
        this.message = message;
    }
}

설명

  • 각 에러 코드는 고유한 codemessage를 가지고 있습니다.
  • 도메인별로 에러 코드를 구분하여 관리합니다.

사용 방법

에러가 발생할 경우 해당하는 ErrorCode를 예외나 응답에 사용합니다.

throw new BusinessException(ErrorCode.BIKE_NOT_FOUND);

또는

return new BaseResponse<>(ErrorCode.PAYMENT_FAILED);

실제 적용 예시

컨트롤러에서의 사용

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public BaseResponse<UserDto> getUser(@PathVariable Long id) {
        UserDto user = userService.findUserById(id);
        if (user == null) {
            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
        }
        return new BaseResponse<>(user);
    }
}

서비스에서의 사용

@Service
public class UserService {

    public UserDto findUserById(Long id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
        return new UserDto(user);
    }
}

기대 효과

  • 일관된 응답 형식: 클라이언트는 항상 동일한 형식의 응답을 받으므로 처리 로직이 간소화됩니다.
  • 유지 보수성 향상: 에러 코드와 메시지를 중앙에서 관리하므로 변경 사항이 있을 때 쉽게 수정할 수 있습니다.
  • 개발 효율성 증대: 개발자들이 예외 처리에 신경 쓰지 않고 비즈니스 로직에 집중할 수 있습니다.