검색해도 잘 안 나오는 스웨거 꿀팁 (w. kotlin, spring boot)

민찬기·2025년 3월 19일

Spring

목록 보기
2/3
post-thumbnail

조금 더 예쁘게 보였으면 하는 마음에, 조금 더 쉽게 작성했으면 하는 마음에 찾기 시작했던 스웨거 설정들입니다.
새로 알아낸 방법이 있을 때마다 업데이트를 진행해보겠습니다.
모든 내용은 Kotlin + Spring Boot를 기반으로 합니다.

프로젝트 Github

😎 기본 설정

페이지 나누기(GroupedOpenApi)

하나의 서버에서 여러 서비스에 대한 명세를 내려주는 경우가 있습니다. 저의 경우 유저들이 사용하는 APP과 어드민 서비스를 제공하는 Web API를 제공했는데, 페이지를 나눠서 제공하면 각 클라이언트 개발자들이 훨씬 보기 좋게 제공할 수 있습니다.

@Configuration
class SwaggerConfig {

	@Bean
    fun appApi(): GroupedOpenApi =
        GroupedOpenApi
            .builder()
            .group("App API")
            .pathsToExclude("/admin/**")
            .build()

    @Bean
    fun adminApi(): GroupedOpenApi =
        GroupedOpenApi
            .builder()
            .group("Admin API")
            .pathsToMatch("/admin/**") // 어드민 API만 포함
            .build()
}            

GroupedOpenApi를 Bean으로 등록하면 되는데, group을 결정짓는 방식은 여러가지가 있습니다. 경로(path) 기반, 패키지(package) 기반 등 여러가지 옵션 중 선택할 수 있습니다.

🤩 요청 설정

파리미터를 객체로 받기(ParameterObject)

흔히 Get 메서드에서 파라미터로 다양한 인자값을 전달합니다. 파라미터가 많아지다보면 Controller Parameter에 @RequestParam 어노테이션이 떡칠 되어 있습니다. 거기에 Valid 어노테이션까지 붙는다면 어지러워지죠.

@Operation(summary = "RequestParam")
@GetMapping("/v1/request-param")
fun requestParam(
    @RequestParam(required = true) year: Int,
    @Min(value = 1L, message = "월은 1월부터 12월까지입니다.")
    @Max(value = 12L, message = "월은 1월부터 12월까지입니다.")
    @RequestParam(required = false) month: Int
): ResponseEntity<SuccessResponse<SchedulePageApiResponseDto>>

그래서 @RequestBody로 요청 객체를 처리하듯, @RequestParam도 동일하게 처리할 수 있습니다. @ParameterObject를 이용하면 됩니다.

@Operation(summary = "ParameterObject")
@GetMapping("/v1/parameter-object")
fun parameterObject(
    @Valid @ParameterObject request: SchedulePageApiRequestDto
): ResponseEntity<SuccessResponse<SchedulePageApiResponseDto>>



data class SchedulePageApiRequestDto(
    @field:Schema(description = "년도", required = true)
    val year: Int,
    @field:Schema(description = "월", required = false)
    @field:Min(value = 1L, message = "월은 1월부터 12월까지입니다.")
    @field:Max(value = 12L, message = "월은 1월부터 12월까지입니다.")
    val month: Int
)

@ParameterObject를 이용하면 @Valid@Schema 어노테이션도 모두 객체 안으로 뺄 수 있습니다. 무엇보다 Controller에서 Service로 인자를 전달할 때 request 통으로 전달할 수 있으므로, 굉장히 편하고 좋습니다.

요청으로 객체 초기화하기(JsonCreator)

파라미터나 바디 형태의 데이터를 요청 시에 전달 받을 때, Primitive Type이 아닌 객체 형태로 데이터를 받을 수 있습니다.

class Version
    @JsonCreator
    constructor(
        @field:Schema(name = "version", description = "버전 정보 (x.y.z)", required = true)
        val value: String
    ) {

        @JsonIgnore
        val major: Int

        @JsonIgnore
        val minor: Int

        @JsonIgnore
        val patch: Int

        init {
            val splited = value.split(".")
            if (splited.size != 3) {
                throw BusinessException(ConfigError.WRONG_VERSION_FORMAT)
            }

            major = splited[0].toInt()
            minor = splited[1].toInt()
            patch = splited[2].toInt()
        }
    }

Parameter

@GetMapping("/v1/operations/force-update")
fun getForceUpdateInfo(
    @ParameterObject request: ForceUpdateInquiryRequest
): ResponseEntity<SuccessResponse<ForceUpdateApiResponseDto>>


data class ForceUpdateInquiryRequest(
    @field:Schema(name = "version", description = "현재 클라이언트 버전", example = "1.2.3", required = true)
    val version: Version,
    @field:Schema(description = "클라이언트 플랫폼", required = true)
    val platform: ClientPlatform
)

Request Body

@PutMapping("/admin/v1/operations/minimum-support-versions")
fun updateMinSupportVersion(
    @RequestBody request: AdminMinSupportVersionUpdateRequest
): ResponseEntity<Unit>


data class AdminMinSupportVersionUpdateRequest(
    @Schema(description = "플랫폼")
    val platform: ClientPlatform,
    @Schema(description = "최소 지원 버전", example = "1.2.3", required = true)
    val version: Version
)

객체 형태의 데이터임에도 불구하고 단순한 String인 것처럼 노출됩니다. 물론 실제 요청에서도 String 형태로 보내면 Version으로 잘 초기화 됩니다.

🥸 응답 설정

204 응답 Content 비워놓기

서버에서 204(No Content)를 내려주는 것을 표시하기 위해 ApiResponse를 다음과 같이 설정할 때가 있습니다. 불필요하게 Example Value 영역이 생기면서 공간을 많이 잡아먹게 됩니다.

@ApiResponse(responseCode = "204")

ApiResponse에 텅 빈 content를 넣게 되면 Example Value를 포함하여 아무것도 남기지 않을 수 있습니다.

@ApiResponse(
    responseCode = "204",
    content = [Content()]
)

태그 블럭 높이 맞추기

API를 묶는 단위인 Tag의 높이가 달라 불-편한 경우가 있습니다. description을 지정하지 않은 경우에 이런 상황이 발생합니다.

TMI라면, description이 있는 경우 위 아래로 margin이 생기면서 Tag의 name(제목)보다 더 큰 영역을 차지하게 됩니다.

그래서 저는 별다른 설명할 게 없는 경우 description에 언더스코어(_)를 넣어둡니다. 티도 잘 안 나고 좋습니다.

공통 에러 처리하기

공통 에러 처리는 어떻게 하면 좋을까요?

공통 에러 처리는 공식문서에 따라 OpenApiCustomizer를 통해 API 명세서를 커스터마이징 할 수 있습니다.

해당 내용은 너무 길어서 제 블로그의 다른 글에 설명을 기입해두었습니다.

profile
https://github.com/devmizz

0개의 댓글