Spring Boot에서 API 응답을 구조화하는 가장 좋은 방법Backend/Spring2024. 11. 24. 00:29
Table of Contents
반응형
오늘은 Spring Boot에서 API 응답을 깔끔하고 일관되며 사용하기 쉽게 구조화하는 가장 좋은 방법에 대해 이야기해보려고 합니다. 이 글을 끝까지 읽으면, 당신의 API가 더 깔끔하고 일관성 있으며, 사용자 친화적으로 바뀌는 모습을 볼 수 있을 것입니다.
API 응답 구조가 왜 중요할까?
먼저, 잘 구조화된 API 응답이 왜 중요한지 살펴봅시다. 일관된 응답 구조는 다음과 같은 장점을 제공합니다:
- 클라이언트 측 에러 처리 개선: 프론트엔드 팀에서 크게 감사할 것입니다.
- 가독성과 유지보수성 향상: 미래의 당신이나 팀이 명확함에 감동할 것입니다.
- 디버깅과 로깅 간소화: 문제를 빠르고 효율적으로 파악할 수 있습니다.
좋은 API 응답의 조건
잘 구조화된 API 응답은 다음과 같은 특징을 가져야 합니다:
- 일관성: 다양한 엔드포인트에서 동일한 형식 유지
- 정보 제공: 관련 데이터, 메시지, 상태 코드 및 에러 코드를 포함
- 단순함: 쉽게 파싱하고 이해할 수 있는 형식
이상적인 응답 구조 만들기
1. 표준 응답 형식 정의
모든 API가 따를 표준 응답 형식을 먼저 정의합니다. 다음은 간단하면서도 효과적인 형식입니다.
{
"success": true,
"message": "요청이 성공적으로 처리되었습니다.",
"data": { ... },
"errors": null,
"errorCode": 0,
"timestamp": 1633017600000,
"path": "/api/example"
}
각 필드의 역할
success
: (boolean
) 요청이 성공했는지 여부를 나타냅니다.message
: (String
) 요청 처리 결과에 대한 사람이 읽을 수 있는 메시지를 제공합니다.data
: (T
) 클라이언트가 요청한 실제 데이터를 포함합니다.errors
: (List<String>
) 요청이 실패한 경우 발생한 에러 메시지 목록입니다.errorCode
: (int
) 비즈니스 로직 관련 에러 유형을 나타내는 코드입니다.timestamp
: (long
) 응답이 생성된 시간을 나타냅니다.path
: (String
) 호출된 API 엔드포인트를 나타냅니다.
2. 응답 유틸리티 메서드 생성
코드 중복을 피하기 위해 응답을 생성하는 유틸리티 메서드를 만들어봅니다.
public class ResponseUtil {
public static <T> ApiResponse<T> success(T data, String message, String path) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(true);
response.setMessage(message);
response.setData(data);
response.setErrors(null);
response.setErrorCode(0);
response.setTimestamp(System.currentTimeMillis());
response.setPath(path);
return response;
}
public static <T> ApiResponse<T> error(List<String> errors, String message, int errorCode, String path) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(false);
response.setMessage(message);
response.setData(null);
response.setErrors(errors);
response.setErrorCode(errorCode);
response.setTimestamp(System.currentTimeMillis());
response.setPath(path);
return response;
}
public static <T> ApiResponse<T> error(String error, String message, int errorCode, String path) {
return error(Arrays.asList(error), message, errorCode, path);
}
}
3. 전역 예외 처리 구현
전역적으로 예외를 처리하면 처리되지 않은 에러도 표준 응답 형식으로 반환할 수 있습니다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleException(HttpServletRequest request, Exception ex) {
List<String> errors = Arrays.asList(ex.getMessage());
ApiResponse<Void> response = ResponseUtil.error(errors, "An error occurred", 1000, request.getRequestURI());
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(HttpServletRequest request, ResourceNotFoundException ex) {
ApiResponse<Void> response = ResponseUtil.error(ex.getMessage(), "Resource not found", 1001, request.getRequestURI());
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ApiResponse<Void>> handleValidationException(HttpServletRequest request, ValidationException ex) {
ApiResponse<Void> response = ResponseUtil.error(ex.getErrors(), "Validation failed", 1002, request.getRequestURI());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
}
4. 컨트롤러에서 응답 형식 사용
표준화된 응답 형식을 컨트롤러에서도 활용합니다.
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Product>> getProductById(@PathVariable Long id, HttpServletRequest request) {
Product product = productService.findById(id);
if (product == null) {
throw new ResourceNotFoundException("Product not found with id " + id);
}
ApiResponse<Product> response = ResponseUtil.success(product, "Product fetched successfully", request.getRequestURI());
return new ResponseEntity<>(response, HttpStatus.OK);
}
@PostMapping
public ResponseEntity<ApiResponse<Product>> createProduct(@RequestBody Product product, HttpServletRequest request) {
Product createdProduct = productService.save(product);
ApiResponse<Product> response = ResponseUtil.success(createdProduct, "Product created successfully", request.getRequestURI());
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
}
에러 코드의 예시
아래는 전자상거래 애플리케이션에서 사용할 수 있는 에러 코드의 예입니다.
에러 코드 | 설명 |
---|---|
2000 | 재고 없음 |
2001 | 결제 수단 거부 |
2002 | 유효하지 않은 쿠폰 코드 |
2003 | 주문 취소 기간 초과 |
2004 | 계정 일시 정지 |
2005 | 동일 상품에 대한 중복 주문 |
마무리
Spring Boot에서 API 응답을 구조화하는 가장 좋은 방법을 알아보았습니다. 위 단계를 구현하면 API가 더 깔끔하고 유지보수가 쉬워질 것입니다.
반응형
'Backend > Spring' 카테고리의 다른 글
Spring Boot에서 Lombok을 활용한 효율적인 Java 개발 가이드 🌟 (0) | 2024.12.02 |
---|---|
Spring Boot에서 데이터 캐싱 방법 (0) | 2024.11.13 |
[Spring Boot] 대용량 데이터 쿼리 REST 엔드포인트 처리 (3) | 2024.11.10 |
[Spring Boot] MultipartFile transferTo() 사용 파일 저장시 주의사항 (0) | 2023.01.10 |
[Spring Boot] MultipartFile + Modal 사용 파일 업로드 (0) | 2023.01.06 |
@고지니어스 :: 규니의 개발 블로그
IT 기술과 개발 내용을 포스팅하는 블로그
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!