0. 배경
Swagger를 사용하여 Api 명세로 의사소통 없이 API에 대한 요청과 응답을 확실하게 하고 싶었습니다.
@ApiResponses를 이용하여 응답값에 대한 모든 예외를 처리하였는데, 문제점이 많아 어떻게 하면 편리하게 사용하고 보여줄 수 있는지 고민하였고 해결 과정입니다.
1. 기존 방식의 문제점
1.1 정확한 값이 나오지 않는다.
Swagger에서는 @RestControllerAdvice와 같은 어노테이션을 이용한 전역 예외 처리를 식별하여 문서화하는 기능이 포함되어있습니다.
이렇게 되었을 때, 400에 대한 무슨 에러인지 어떤 메세지가 발생하는지에 대한 예외를 알 수 없습니다.
1.2 responseCode, description을 직접 작성해야 한다.
같은 에러코드를 묶고, 정확한 설명을 위해서 수작업을 해야 합니다.
컨트롤러가 너무 길고 가독성이 좋지 않습니다.
1.3 ExampleObject를 직접 작성해야 한다.
예외에 대한 공통 응답이 존재하여도, 이렇게 직접 작성해야 하기 때문에 불편함이 너무 큽니다.
분명히 실수를 할 수 있고, 컨트롤러가 너무 길어지기 때문에 안 좋습니다.
중요한 프로덕션 코드에 집중을 할 수 없는 상황입니다.
2. 스웨거 타입 분석
Media Type Object 안에 examples 안에는 Example Object가 올 수 있는 것을 확인할 수 있습니다.
Example Object를 직접 커스텀하겠습니다.
3. 커스텀 적용하기
3.1 어노테이션 생성
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiErrorCodeExamples {
ErrorCode[] value();
}
ApiErrorCodeExamples 어노테이션을 만들고 ErrorCode를 받도록 하였습니다.
3.2 어노테이션 정보 불러오기
@Bean
public OperationCustomizer customize() {
return (Operation operation, HandlerMethod handlerMethod) -> {
ApiErrorCodeExamples apiErrorCodeExamples = handlerMethod.getMethodAnnotation(
ApiErrorCodeExamples.class);
// @ApiErrorCodeExamples 어노테이션이 붙어있다면
if (apiErrorCodeExamples != null) {
generateErrorCodeResponseExample(operation, apiErrorCodeExamples.value());
}
return operation;
};
}
SwaggerConfig에 직접 커스텀 메소드를 작성하였습니다.
ApiErrorCodeExamples 어노테이션이 있다면 스웨거 응답 커스텀을 진행합니다.
3.3 응답 커스텀
private void generateErrorCodeResponseExample(Operation operation, ErrorCode[] errorCodes) {
ApiResponses responses = operation.getResponses();
// ExampleHolder(에러 응답값) 객체를 만들고 에러 코드별로 그룹화
Map<Integer, List<ExampleHolder>> statusWithExampleHolders;
statusWithExampleHolders = Arrays.stream(errorCodes)
.map(
errorCode -> ExampleHolder.builder()
.holder(getSwaggerExample(errorCode))
.code(errorCode.getStatus().value())
.name(errorCode.name())
.build()
)
.collect(Collectors.groupingBy(ExampleHolder::getCode));
// ExampleHolders를 ApiResponses에 추가
addExamplesToResponses(responses, statusWithExampleHolders);
}
위 오픈소스에서 스웨거의 응답을 분석했을 때, 여러개의 Example Object가 올 수 있습니다.
// exampleHolder를 ApiResponses에 추가
private void addExamplesToResponses(ApiResponses responses,
Map<Integer, List<ExampleHolder>> statusWithExampleHolders) {
statusWithExampleHolders.forEach(
(status, v) -> {
Content content = new Content();
MediaType mediaType = new MediaType();
ApiResponse apiResponse = new ApiResponse();
v.forEach(
exampleHolder -> mediaType.addExamples(
exampleHolder.getName(),
exampleHolder.getHolder()
)
);
content.addMediaType("application/json", mediaType);
apiResponse.setContent(content);
responses.addApiResponse(String.valueOf(status), apiResponse);
}
);
}
이 형식으로 상태코드 기준으로 응답을 모아 ApiResponses 객체에 넣어주면 됩니다.
4. 최종 결과
Swagger에서 위와 같은 에러 응답을 확인할 수 있습니다.
스웨거가 어떤 구조로 되어있는가를 파악하는 것이 중요합니다.
구조를 이해하시면 커스텀하기 편하실 것입니다.