
Spring에서 기본적으로 제공하는 ResponseEntity<T>를 사용하면 HTTP 상태 코드와 함께 응답을 보낼 수 있지만 내부 API를 설계할 때는 다양한 이유로 커스텀 응답 코드를 사용해야 하는 경우가 있음
내부 API 설계
HTTP 상태 코드만으로 내부 API의 모든 상황을 표현하기 어려움.
→ 특정한 비즈니스 로직에 맞는 상태 코드를 따로 정의할 필요가 있음.
→ 예: A01 (타입 오류), A02 (입력 오류), B01 (외부 서버 통신 오류) 등.
외부 Open API 설계
Open API에서는 HTTP 상태 코드를 엄격하게 준수해야 하지만, 내부적으로도 더 세분화된 응답 처리가 필요할 수 있음.
→ BaseResponse를 활용해 추가적인 상태 코드와 메시지를 전달.
클라이언트(프론트엔드)와의 효율적인 연동
프론트엔드는 응답 코드가 200 OK일 경우 내부 데이터를 신뢰하고 로직을 처리하는 경우가 많음.
→ 예외 발생 시에도 200 OK로 응답하되, 응답 데이터에 실패 상태를 포함하여 처리 가능.
BaseResponse는 보통 아래와 같은 필드를 가집니다.
public class BaseResponse<T> {
private String code; // 커스텀 응답 코드 (ex. A01, B02 등)
private String message; // 응답 메시지
private T data; // 실제 응답 데이터
// 성공 응답을 위한 정적 메서드
public static <T> BaseResponse<T> success(T data) {
return new BaseResponse<>("200", "요청 성공", data);
}
// 실패 응답을 위한 정적 메서드
public static <T> BaseResponse<T> error(String code, String message) {
return new BaseResponse<>(code, message, null);
}
// 생성자 (private으로 숨김)
private BaseResponse(String code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
// Getter 메서드 추가
public String getCode() { return code; }
public String getMessage() { return message; }
public T getData() { return data; }
}
@GetMapping("/user/{id}")
public ResponseEntity<BaseResponse<User>> getUser(@PathVariable Long id) {
User user = userService.findById(id)
.orElseThrow(() -> new CustomException("A02", "사용자를 찾을 수 없습니다."));
return ResponseEntity.ok(BaseResponse.success(user));
}
BaseResponse.success(user)를 사용하여 성공 응답을 생성200 OK, 응답 데이터는 BaseResponse<User> 형태로 반환됨@ExceptionHandler(CustomException.class)
public ResponseEntity<BaseResponse<Void>> handleCustomException(CustomException e) {
return ResponseEntity.ok(BaseResponse.error(e.getCode(), e.getMessage()));
}
200 OK로 보내되, BaseResponse 내부에 실패 코드와 메시지를 포함BaseResponse.code 값을 확인하여 에러 여부를 판단 가능일반적으로 컨트롤러 내부에서 예외 처리를 할 때 try-catch를 사용하는 방식은 유지보수성이 떨어지고 중복 코드가 발생하기 쉽습니다.
이를 해결하기 위해 Spring은 @ControllerAdvice를 제공하며, 공통적인 예외 처리를 한 곳에서 관리할 수 있도록 합니다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomException.class)
public ResponseEntity<BaseResponse<Void>> handleCustomException(CustomException e) {
return ResponseEntity.ok(BaseResponse.error(e.getCode(), e.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<BaseResponse<Void>> handleGlobalException(Exception e) {
return ResponseEntity.ok(BaseResponse.error("500", "서버 내부 오류가 발생했습니다."));
}
}
@RestControllerAdvice : @ControllerAdvice + @ResponseBody 기능을 함@ExceptionHandler(CustomException.class) : CustomException 발생 시 공통적으로 처리@ExceptionHandler(Exception.class) : 예상하지 못한 예외를 처리@RestControllerAdvice는 @ControllerAdvice와 다르게 모든 응답을 JSON 형태로 변환
@ControllerAdvice
public class MyControllerAdvice {
@ExceptionHandler(Exception.class)
public BaseResponse<Void> handleException(Exception e) {
return BaseResponse.error("500", "서버 내부 오류");
}
}
위 코드는 @ResponseBody가 없어 JSON으로 변환되지 않고, HTML 오류 페이지가 반환될 수 있음.
이를 해결하려면 @ResponseBody를 추가하거나 @RestControllerAdvice를 사용해야 합니다.