- 모든 API 응답을 CommonWrapperResponse로 래핑
- 모든 컨트롤러의 응답을 일관된 형식으로 감싸기 위해 ResponseBodyAdvice를 구현함.
- 성공 응답과 에러 응답 모두 공통 포맷으로 제공하도록 함.
- status, data 필드를 포함하는 CommonWrapperResponse 설계.
더보기
package org.example.expert.common.adsd;
import lombok.Getter;
@Getter
public class CommonWrapperResponse {
// Getters and Setters
private int status;
private Object data;
// Builder pattern
public static CommonWrapperResponseBuilder builder() {
return new CommonWrapperResponseBuilder();
}
public static class CommonWrapperResponseBuilder {
private int status;
private Object data;
public CommonWrapperResponseBuilder status(int status) {
this.status = status;
return this;
}
public CommonWrapperResponseBuilder data(Object data) {
this.data = data;
return this;
}
public CommonWrapperResponse build() {
return new CommonWrapperResponse(status, data);
}
}
private CommonWrapperResponse(int status, Object data) {
this.status = status;
this.data = data;
}
}
- ErrorResponse를 사용한 에러 응답 포맷 표준화
- 모든 예외 응답은 ErrorResponse 클래스를 통해 처리.
- 필드:
- message: 사용자에게 보여줄 메시지
- code: HttpStatus 이름 (예: BAD_REQUEST)
- status: HttpStatus 객체 자체
더보기
@Getter
public class ErrorResponse {
private String message;
private String code;
private HttpStatus status;
// Constructor, Getters, Setters
public ErrorResponse(String message, String code, HttpStatus status) {
this.message = message;
this.code = code;
this.status = status;
}
}
- @RestControllerAdvice로 예외 전역 처리
- InvalidRequestException, AuthException, ServerException, Exception 등을 전역 처리함.
- 응답 코드와 메시지를 명확히 지정하여 클라이언트에서 쉽게 파싱 가능.
더보기
package org.example.expert.common.adsd;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.example.expert.common.exception.ErrorResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.List;
@RestControllerAdvice
public class CommonControllerAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return !Void.TYPE.equals(returnType.getParameterType());
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof ErrorResponse) {
ErrorResponse errorResponse = (ErrorResponse) body;
HttpStatus httpStatus = errorResponse.getStatus();
response.setStatusCode(httpStatus);
return wrapResponse(errorResponse, httpStatus);
}
return wrapResponse(body, HttpStatus.OK);
}
private CommonWrapperResponse wrapResponse(Object response, HttpStatus status) {
return CommonWrapperResponse.builder()
.status(status.value())
.data(response)
.build();
}
}
- Validation 예외 처리 (MethodArgumentNotValidException)
- 필드 검증 실패 시 발생하는 예외에 대해 각 필드별 메시지를 Map<String, String> 형태로 반환.
- 추후 ErrorResponse로 통합 가능성 고려.
- 빈 리스트 응답 예외 처리
- ResponseBodyAdvice 내부에서 리스트가 비어있는 경우, 정의되지 않은 오류 메시지와 함께 500 응답 반환 처리.
🧠 배운 점
- ResponseBodyAdvice를 활용하면 모든 API 응답에 대해 공통 포맷을 적용할 수 있음.
- HttpStatus를 직접 응답 객체에 포함시키면 클라이언트 입장에서 에러 처리 및 디버깅이 훨씬 쉬움.
- supports() 메서드에서 Void.TYPE을 제외하면 void 리턴 메서드는 자동으로 제외되어 불필요한 래핑을 방지할 수 있음.
- 전역 예외 처리기(@RestControllerAdvice)는 예외 처리 로직을 각 컨트롤러에서 분리시켜 코드의 가독성과 재사용성을 높여줌.
🤔 다음에 개선할 점
- MethodArgumentNotValidException 응답도 ErrorResponse 포맷으로 통일할 예정.
- 응답에 timestamp, path, requestId 등을 추가하여 로깅 및 디버깅에 유용하게 만들기.
- Swagger/OpenAPI 문서와 연동하여 예외 응답 포맷도 명세서에 반영되도록 개선.
- 필요 시 ExceptionCode enum을 도입해 에러코드 관리 고도화.
- API 응답은 무조건 200 OK로 통일하고 내부적으로 ErrorResponse 또는 비즈니스 코드로 판단하는 구조는 프론트엔드 협업과 유지보수에 매우 유리.
💡 느낀 점
하나의 API에서도 성공, 실패 응답이 다양하게 나올 수 있기 때문에, 통일된 구조로 관리하는 건 생각보다 훨씬 중요하다.
특히 프론트엔드와 협업 시, 공통 포맷 덕분에 개발 및 디버깅이 매우 수월해질 것으로 기대된다.
이번 구조는 앞으로 다른 프로젝트에도 충분히 재사용 가능할 만큼 잘 정리된 느낌!