API 문서를 생성할 때 심플하면서 API를 직접 호출 할 수 있도록 Swagger를 주로 사용한다.
그리고 Spring을 사용하여 Swagger를 사용하기 위해 springdoc-openapi를 사용한다.
springdoc-openapi starter 의존성을 추가하고 서버 애플리케이션을 실행하면 Controller의 API 요청과 응답을 스캔하여 기본 API 문서가 생성된다.
하지만 서버 애플리케이션은 항상 정상 응답만 주는 것은 아니다.
상황에 따라 만료된 토큰을 사용했을 때 인가 실패 응답을 주거나 예상치 못한 오류에 대한 응답을 줄 수도 있다.
이번 포스팅에서는 Swagger에 전체 API에서 응답할 수 있는 공통적인 오류 응답을 추가하는 방법에 대해서 알아보도록 하겠다.
공통 오류 응답
서비스 오류 시 응답하는 형태에 대해서 응답 DTO를 만든다.
import io.swagger.v3.oas.annotations.media.Schema
@Schema(description = "오류 응답")
data class ErrorResponse(
@Schema(description = "오류 코드")
val code: String,
@Schema(description = "오류 메세지")
val message: String,
)
Swagger 설정
API에 문서를 작성하기 위해서 @Operation을 사용했었을 것이다.
이름에서처럼 각 API는 하나의 Operation이다.
springdoc-openapi 에서는 Operation을 @Operation 뿐만이 아니라 programmatic하게도 변경할 수 있도록 OperationCustomizer를 제공한다.
이 OperationCustomizer를 통해서 공통 에러에 대한 응답을 추가해주면 된다.
OperationCustomizer bean을 등록하는 예제 코드는 아래와 같다.
import io.swagger.v3.oas.models.examples.Example
import io.swagger.v3.oas.models.media.Content
import io.swagger.v3.oas.models.media.MediaType
import io.swagger.v3.oas.models.responses.ApiResponse
import org.springdoc.core.customizers.OperationCustomizer
import org.springdoc.core.models.GroupedOpenApi
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class SwaggerConfiguration {
@Bean
fun meetingApi(): GroupedOpenApi {
return GroupedOpenApi.builder()
.group("XX Service API")
.pathsToMatch("/**")
.addOperationCustomizer(this.operationCustomizer())
.build()
}
/**
* API operation에 오류 응답을 등록한다
*/
@Bean
fun operationCustomizer(): OperationCustomizer {
return OperationCustomizer { operation, handlerMethod ->
operation.responses
.addApiResponse(
"401",
ApiResponse().content(
Content().addMediaType(
"application/json",
MediaType()
.addExamples(
"AUTH_UNAUTHORIZED",
Example().value(ErrorResponse(code = "AUTH_UNAUTHORIZED", message = "로그인을 해주세요."))
)
)
)
)
.addApiResponse(
"500",
ApiResponse().content(
Content().addMediaType(
"application/json",
MediaType()
.addExamples(
"UNEXPECTED_ERROR",
Example().value(ErrorResponse(code = "UNEXPECTED_ERROR", message = "예상치 못한 오류가 발생했습니다."))
)
.addExamples(
"DB_ERROR",
Example().value(ErrorResponse(code = "DB_ERROR", message = "DB 오류가 발생했습니다."))
)
)
)
)
operation
}
}
}
그리고 Controller를 만들고 몇가지 API를 만든 뒤,
서버를 실행시키고 /swagger-ui/index.html 에 접속하여 API를 살펴보면 공통적으로 위에서 설정한 공통 오류 응답이 등록되어있는 것을 확인할 수 있다.
주의해야할 점은 Swagger 문서 설정은 주로 key, value 형식이기 때문에 key가 겹치면 설정이 append가 되는 것이 아니라 overwrite 되는 경우가 많다.
그렇기 때문에 설정 시 status code나 example key 등이 겹쳐지지 않는지 확인할 필요가 있다.
또한 알아야할 점이 OperationCustomizer은 Operation 별로 즉, 각 API endpoint 별로 처리된다.
그렇기 때문에 OperationCutomizer 구현체에서 다음과 같이 Controller 메서드에 접근할 수 있게된다.
@Bean
fun operationCustomizer(): OperationCustomizer {
return OperationCustomizer { operation, handlerMethod ->
println("${handlerMethod.method.declaringClass.simpleName}.${handlerMethod.method.name} operation 처리 중")
operation.responses
...
이를 응용하여 Controller method에 걸린 annotation이나 파라미터를 보고 Operation을 커스터마이징 할 수도 있다.
'Java & Kotlin > Spring' 카테고리의 다른 글
여러 유저를 사용한 E2E test (RestAssured 사용) (0) | 2025.05.06 |
---|---|
Spring graceful shutdown (+ async, virtual thread) (1) | 2025.03.09 |
Kotlin SpringBoot 환경에서 jOOQ 설정 (2) | 2024.12.03 |
[Spring] Swagger UI 대신 Scalar API Reference를 사용하여 API 문서 사용하기 (1) | 2024.10.29 |
[Spring Data JPA] @OneToMany Entity 연관 관계에 대하여 (7) | 2023.01.20 |
댓글