ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot 시리즈 5편 – API 응답 구조 표준화 전략: 일관된 성공/실패 응답 설계하기
    기술과 산업/언어 및 프레임워크 2025. 4. 17. 20:15
    728x90

    Spring Boot에서 API 응답 형식을 일관되게 유지하는 방법을 소개합니다. ApiResponse, ApiError 구조 설계로 성공과 실패 응답을 표준화합니다.


    Spring Boot 시리즈 5편 – API 응답 구조 표준화 전략: 일관된 성공/실패 응답 설계하기

    REST API를 설계하면서 많은 개발자가 놓치는 부분 중 하나가 응답 구조의 통일성입니다.
    이번 편에서는 실무에서 바로 적용할 수 있는 성공/실패 응답 구조의 표준화 방식,
    그리고 API 일관성을 높이는 설계 전략을 소개합니다.


    🔍 왜 응답 구조를 통일해야 할까?

    • 프론트엔드 개발자와의 협업 시 가독성과 처리 효율 증가
    • 에러 처리 및 디버깅의 일관성 확보
    • 테스트 코드 작성 및 API 문서화(예: Swagger)에서 이점
    • 기업 API 스타일 가이드라인에도 필수 요소

    📦 1. 기본 응답 형식 정의 – ApiResponse

    ✅ 공통 응답 포맷 설계 예시

    {
      "success": true,
      "code": 200,
      "message": "요청이 성공적으로 처리되었습니다.",
      "data": {
        "name": "홍길동",
        "email": "hong@test.com"
      }
    }
    

    ✅ 공통 응답 객체 클래스

    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    public class ApiResponse<T> {
        private boolean success;
        private int code;
        private String message;
        private T data;
    
        public static <T> ApiResponse<T> success(T data) {
            return new ApiResponse<>(true, 200, "성공", data);
        }
    
        public static <T> ApiResponse<T> error(int code, String message) {
            return new ApiResponse<>(false, code, message, null);
        }
    }
    

    ❌ 2. 실패 응답 구조 – ApiError

    @Getter
    @AllArgsConstructor
    public class ApiError {
        private String field;
        private String message;
    }
    

    검증 실패 시 예외 핸들러에서 해당 구조로 리턴할 수 있습니다:

    List<ApiError> errors = ex.getBindingResult().getFieldErrors().stream()
        .map(err -> new ApiError(err.getField(), err.getDefaultMessage()))
        .collect(Collectors.toList());
    return ResponseEntity.badRequest().body(ApiResponse.error(400, "입력값 오류").withErrors(errors));
    

    🧠 3. Controller에서의 적용 예시

    @PostMapping
    public ResponseEntity<ApiResponse<UserDto>> createUser(@Valid @RequestBody UserDto dto) {
        UserDto saved = userService.createUser(dto);
        return ResponseEntity.ok(ApiResponse.success(saved));
    }
    

    모든 컨트롤러 응답을 ApiResponse<T>로 통일하면, 클라이언트는 항상 동일한 구조로 데이터를 파싱할 수 있습니다.


    ⚙️ 4. 글로벌 예외 처리에서의 활용

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResponse<List<ApiError>>> handleValidationError(MethodArgumentNotValidException ex) {
        List<ApiError> errors = ex.getBindingResult().getFieldErrors().stream()
            .map(err -> new ApiError(err.getField(), err.getDefaultMessage()))
            .collect(Collectors.toList());
    
        return ResponseEntity.badRequest().body(ApiResponse.error(400, "유효성 검사 실패", errors));
    }
    

    🧱 5. 상태코드와 메시지 관리 전략

    ✅ 상태 코드/메시지를 상수화

    public class ApiCode {
        public static final int OK = 200;
        public static final int VALIDATION_ERROR = 400;
        public static final int INTERNAL_ERROR = 500;
    }
    

    또는 Enum으로 관리해도 좋습니다:

    @Getter
    @AllArgsConstructor
    public enum ApiStatus {
        OK(200, "성공"),
        VALIDATION_ERROR(400, "유효성 오류"),
        SERVER_ERROR(500, "서버 오류");
    
        private final int code;
        private final String message;
    }
    

    ✅ 마무리 요약

    항목 내용

    응답 포맷 success, code, message, data 필드로 통일
    에러 구조 ApiError 리스트로 필드별 메시지 제공
    적용 위치 Controller, ExceptionHandler 전면 적용
    확장성 Enum/상수 기반 상태코드, 메시지 관리 가능
    장점 협업, 유지보수, API 문서화(예: Swagger)에서 강력한 일관성 확보

    📌 다음 편 예고

    Spring Boot 시리즈 6편: Swagger를 활용한 API 문서 자동화 전략 – 실무 적용 가이드

    728x90
Designed by Tistory.