ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot 시리즈 28편 – 예외 처리 전략 고도화: 에러 응답 표준화와 API 일관성 유지
    기술과 산업/언어 및 프레임워크 2025. 5. 9. 14:39
    728x90

    Spring Boot에서 전역 예외 처리(@ControllerAdvice)를 중심으로 API 에러 응답을 통일된 형식으로 설계하는 전략을 소개합니다. 실무용 커스텀 예외 구조와 응답 UX 개선 팁 포함.


    Spring Boot 시리즈 28편 – 예외 처리 전략 고도화: 에러 응답 표준화와 API 일관성 유지

    API 품질을 결정짓는 핵심 요소 중 하나는 에러 응답의 일관성입니다.
    "왜 에러가 났는지", "어떻게 대응해야 하는지"가 명확하지 않다면
    클라이언트는 혼란스러워지고, 유지보수 비용은 기하급수적으로 늘어납니다.

    Spring Boot의 전역 예외 처리 기능을 활용해,
    정형화된 에러 응답 구조상황별 커스텀 예외 처리 전략을 체계적으로 정리합니다.


    📌 1. 에러 응답 구조 통일의 필요성

    문제 상황 통일된 구조가 없다면?

    유효성 검사 실패 400, 422 등 중구난방 코드 + 메시지 누락
    존재하지 않는 ID HTTP 500으로 통일되어 내부 에러처럼 보임
    인증/인가 실패 로그인 실패와 권한 없음이 구분되지 않음

    → 클라이언트 입장에선 API마다 에러 해석을 다르게 해야 함
    → 서버 개발자 입장에서도 디버깅/로깅/모니터링 어려움


    ✅ 2. 통일된 에러 응답 포맷 설계

    예시: 공통 에러 응답 스펙

    {
      "success": false,
      "code": "USER_NOT_FOUND",
      "message": "해당 사용자가 존재하지 않습니다.",
      "timestamp": "2025-04-28T14:35:20",
      "status": 404,
      "path": "/api/users/99"
    }
    

    필드 설명

    success 성공 여부 (false)
    code 식별 가능한 오류 코드
    message 사용자에게 보여줄 메시지
    status HTTP 상태 코드
    timestamp 에러 발생 시간
    path 요청 경로

    🛠️ 3. 커스텀 예외 클래스 설계

    @Getter
    public class CustomException extends RuntimeException {
        private final ErrorCode errorCode;
    
        public CustomException(ErrorCode code) {
            super(code.getMessage());
            this.errorCode = code;
        }
    }
    

    ErrorCode enum 설계

    @Getter
    @AllArgsConstructor
    public enum ErrorCode {
        USER_NOT_FOUND(404, "해당 사용자가 존재하지 않습니다."),
        INVALID_INPUT(400, "입력값이 올바르지 않습니다."),
        INTERNAL_SERVER_ERROR(500, "서버 오류가 발생했습니다.");
    
        private final int status;
        private final String message;
    }
    

    → 각 예외는 명확한 code + message + status 조합을 갖게 됨


    🧩 4. 전역 예외 처리기 구성 (@ControllerAdvice)

    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(CustomException.class)
        public ResponseEntity<ErrorResponse> handleCustomException(CustomException e, HttpServletRequest request) {
            ErrorCode code = e.getErrorCode();
            ErrorResponse response = ErrorResponse.of(code, request.getRequestURI());
            return ResponseEntity.status(code.getStatus()).body(response);
        }
    
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException e, HttpServletRequest request) {
            ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT, request.getRequestURI(), e);
            return ResponseEntity.status(400).body(response);
        }
    
        @ExceptionHandler(Exception.class)
        public ResponseEntity<ErrorResponse> handleGeneral(Exception e, HttpServletRequest request) {
            ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR, request.getRequestURI());
            return ResponseEntity.status(500).body(response);
        }
    }
    

    🧾 5. ErrorResponse DTO 설계

    @Getter
    @Builder
    public class ErrorResponse {
    
        private final boolean success = false;
        private final String code;
        private final String message;
        private final int status;
        private final String path;
        private final String timestamp;
    
        public static ErrorResponse of(ErrorCode code, String path) {
            return ErrorResponse.builder()
                    .code(code.name())
                    .message(code.getMessage())
                    .status(code.getStatus())
                    .path(path)
                    .timestamp(LocalDateTime.now().toString())
                    .build();
        }
    
        public static ErrorResponse of(ErrorCode code, String path, Exception e) {
            return of(code, path); // 필요 시 e.getMessage() 등을 확장 가능
        }
    }
    

    🎯 6. 응답 UX 향상을 위한 전략

    상황 UX 전략

    유효성 오류 상세 필드 정보까지 함께 반환 (fieldErrors)
    인증 실패 401 Unauthorized + 재로그인 유도 메시지
    인가 실패 403 Forbidden + 접근 불가 안내
    시스템 에러 500 내부 메시지는 숨기고 외부 메시지는 명확히

    ✅ 마무리 요약

    항목 요약

    공통 구조 code + message + status + path + timestamp 구성
    예외 클래스 CustomException, ErrorCode enum으로 표준화
    전역 처리 @RestControllerAdvice로 공통 처리
    UX 향상 유저와 개발자 모두를 위한 구조화된 메시지 제공
    확장 전략 ResponseEntity 외 API Gateway 표준과 통합 가능

    📌 다음 편 예고

    Spring Boot 시리즈 29편: RestTemplate vs WebClient – 외부 API 통신 전략 비교와 적용 가이드

    728x90
Designed by Tistory.