기술과 산업/언어 및 프레임워크
Spring Boot 시리즈 7편 – API 응답 구조 표준화 전략: 성공과 실패를 구분하는 통일 설계
B컷개발자
2025. 4. 23. 20:00
728x90
Spring Boot REST API의 응답 구조를 표준화하는 전략을 소개합니다. 성공/실패 응답 포맷 통일, 공통 응답 객체 설계, 예외 처리 통합까지 포함합니다.
Spring Boot 시리즈 7편 – API 응답 구조 표준화 전략: 성공과 실패를 구분하는 통일 설계
REST API에서 가장 흔한 실수 중 하나는 각 API마다 다른 응답 구조입니다.
예를 들어 성공 응답은 그냥 JSON 객체로, 실패 응답은 상태 코드만 주거나,
경우에 따라 메시지 형태도 제각각이라면 프론트엔드와 앱 개발자는 매번 예외 처리를 따로 해야 하는 불편함을 겪게 됩니다.
이번 편에서는 Spring Boot 환경에서 API 응답을 성공/실패 케이스 모두 포함하여 통일된 형식으로 설계하고,
예외 상황까지 일관성 있게 처리하는 방법을 소개합니다.
📌 1. 왜 응답 구조를 표준화해야 하나?
- 프론트/앱 개발자와 협업이 쉬워짐
- 에러 발생 시 원인 추적과 디버깅 속도 증가
- API 명세(Swagger)에서 응답 구조 일관성 확보
- 테스트 코드에서 응답 구조 검증이 단순화됨
✅ 2. 통일된 공통 응답 포맷 설계
가장 많이 쓰이는 형식은 다음과 같습니다:
{
"success": true,
"code": "S200",
"message": "정상 처리되었습니다.",
"data": {
"id": 1,
"name": "홍길동"
}
}
🔁 실패 응답 예시
{
"success": false,
"code": "C001",
"message": "필수 파라미터 누락",
"errors": [
{
"field": "email",
"message": "이메일은 필수입니다."
}
]
}
🧱 3. 공통 응답 객체 클래스 구성
1️⃣ ApiResponse<T> 클래스
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
private boolean success;
private String code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, "S200", "요청이 성공적으로 처리되었습니다.", data);
}
public static <T> ApiResponse<T> fail(String code, String message, T errorDetail) {
return new ApiResponse<>(false, code, message, errorDetail);
}
}
2️⃣ FieldErrorResponse
@Getter
@AllArgsConstructor
public class FieldErrorResponse {
private String field;
private String message;
}
🛠️ 4. Controller에서의 적용 예시
@PostMapping
public ResponseEntity<ApiResponse<UserDto>> register(@Valid @RequestBody RegisterUserRequest request) {
UserDto result = userService.register(request);
return ResponseEntity.ok(ApiResponse.success(result));
}
❌ 예외 응답 통합 처리
1️⃣ BusinessException 기반 커스텀 예외 정의
@Getter
public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
}
2️⃣ ErrorCode Enum 예시
@Getter
@AllArgsConstructor
public enum ErrorCode {
INVALID_INPUT("C001", "입력값이 올바르지 않습니다."),
DUPLICATE_EMAIL("U001", "이미 사용 중인 이메일입니다."),
INTERNAL_ERROR("S500", "서버 내부 오류");
private final String code;
private final String message;
}
3️⃣ GlobalExceptionHandler 통합 처리
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<?>> handleBusinessException(BusinessException e) {
ErrorCode code = e.getErrorCode();
return ResponseEntity.badRequest().body(ApiResponse.fail(code.getCode(), code.getMessage(), null));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<List<FieldErrorResponse>>> handleValidationException(MethodArgumentNotValidException ex) {
List<FieldErrorResponse> errors = ex.getBindingResult().getFieldErrors().stream()
.map(err -> new FieldErrorResponse(err.getField(), err.getDefaultMessage()))
.toList();
return ResponseEntity.badRequest().body(ApiResponse.fail("C001", "유효성 검사 실패", errors));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleAll(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.fail("S500", "알 수 없는 오류가 발생했습니다.", null));
}
}
🧠 실무 설계 팁
항목 설계 전략
| 공통 응답 구조 | success, code, message, data 필드 유지 |
| Swagger 문서화 | @ApiResponse에 응답 구조 통일 |
| 오류 코드 관리 | ErrorCode Enum으로 체계적 관리 |
| 테스트 코드 | JSON 구조 검증 → jsonPath("success").value(true) 등 |
| 코드 통일 | 모든 Controller는 ApiResponse<T>로 반환 통일 |
✅ 마무리 요약
항목 요약
| 핵심 클래스 | ApiResponse<T>, ErrorCode, FieldErrorResponse |
| 예외 통합 처리 | @RestControllerAdvice + BusinessException 구조 |
| 표준 응답 포맷 | 성공/실패 구조를 동일하게 유지 |
| 협업 효과 | 프론트/앱 개발자, QA 모두 예측 가능한 처리 가능 |
| Swagger 연계 | 명세 자동화와 테스트 연계에 유리 |
📌 다음 편 예고
Spring Boot 시리즈 8편: 입력값 검증 전략 – @Valid, 커스텀 Validator, 도메인 유효성 설계까지
728x90