티스토리 뷰

 

일단 나는 API 문서화 + 공유용으로 Postman을 더 선호한다.

 

그런데 내가 Postman을 선호하고 잘 쓸 수 있었던 가장 큰 이유는 팀 규모가 크지 않았기 때문이라 생각한다.

 

팀 규모가 커지면 Postman은 상당히 비싼 툴이기 때문이다.

 

한 명당 가격임 basic 2.5만 professional 5만(내가 대표여도 안사줌)

 

팀 규모가 커져서 Postman을 놓아줄 경우 대안은 여러 종류가 있지만, OpenAPI + Swagger-ui 를 많이 쓴다.

 

그런데 난 Swagger를 선호하지 않는다.

 

거기엔 이유가 몇가지 있다.

1. SpringBoot에서 쓰기엔 코드에 너무 많은 어노테이션들이 들어가서, 오히려 코드의 가독성을 망친다.
2. springdoc, springfox에 spring 버전, openapi 버전, java/kotlin 버전에 따라 사용법이 너무 달라서 잘 쓰기 어려움.
3. ui가 깔끔하긴한데, 파라미터가 많을 경우 요청을 보내고 확인하는데 불편함.

 

1번 이유가 너무 컸다. 

 

이번에 기회가 닿아 최신화된 OpenAPI3 규격에 맞춰 써봤는데, 내가 생각하는 한 두가지 불편함을 제외하면 나름 괜찮게 테스트해 본 것 같다.

 

내가 테스트해본 것들은 아래와 같다.

1. Docket 생성
2. Get/Post API 생성하고, 변수를 입력 받아 테스트 가능한가 확인
3. 요청 객체의 파라미터 검증이 되나 확인
4. API/요청객체/응답객체 description이 잘 출력되나 확인
5. API 별 에러코드 추가
6. 공통 에러처리 추가(Schema가 안나오는데 원인 파악이 안됨)7. JWT 설정(검증은 없음)

 

차근차근 진행해보자.

UI가 진짜 이쁘긴 하다.

코드

build.gradle

파라미터 검증을 위해 validation을 추가함

implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0")
implementation("org.springframework.boot:spring-boot-starter-validation")

 

configuration

configuration에서 많은 부분이 설정된다.

대표적으로 Docket(최상단 타이틀과 Description 설정), 공통 에러 처리, JWT 토큰이 설정된다.

 

1. Docket

@Configuration
class SwaggerConfig {

    @Bean
    fun openAPI(): OpenAPI {
        return OpenAPI()
                .components(Components())
                .info(configurationInfo())
    }

    private fun configurationInfo(): Info {
        return Info()
                .title("OpenAPI3 UI 테스트")
                .description("OpenAPI3 - Springdoc을 사용한 Swagger UI 테스트")
                .version("1.0.0")
    }
}

 

이부분 코드임

2. 공통 에러처리

여기가 좀 귀찮았음

@Configuration
class SwaggerConfig {
    @Bean
    fun customiseResponses(): OpenApiCustomizer {
        return OpenApiCustomizer { openApi: OpenAPI ->
            openApi.paths.orEmpty().values.forEach { pathItem ->
                pathItem.readOperations().forEach { operation ->
                    val apiResponses: ApiResponses = operation.responses

                    // 기존 응답들을 유지하면서 표준 응답을 추가
                    apiResponses.addApiResponse("400", createStandardApiResponse("400", "Bad Request"))
                    apiResponses.addApiResponse("500", createStandardApiResponse("500", "Internal Server Error"))
                }
            }
        }
    }

    private fun createStandardApiResponse(code: String, description: String): ApiResponse {
        val exampleContent = when (code) {
            "400" -> """
            {
                "code": "400",
                "message": "잘못된 요청입니다.",
                "timestamp": "2023-01-01T12:00:00Z"
            }
            """.trimIndent()

            "500" -> """
            {
                "code": "500",
                "message": "서버 오류가 발생했습니다.",
                "timestamp": "2023-01-01T12:00:00Z"
            }
            """.trimIndent()

            else -> ""
        }

        val example = Example().apply {
            value = exampleContent
        }

        val mediaType = MediaType().apply {
            addExamples("example", example)
            schema = Schema<Any>() // ErrorResponse의 스키마를 참조하거나 일반 Object 스키마를 사용할 수 있습니다.
        }

        val content = Content().apply {
            addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE, mediaType)
        }

        return ApiResponse().apply {
            this.description = description
            this.content = content
        }
    }
}

모든 API에 대해 이렇게 처리된다.

3. JWT 

생각보다 간단함

@Configuration
@SecurityScheme(name = "bearerAuth", type = SecuritySchemeType.HTTP, bearerFormat = "JWT", scheme = "bearer")
class SwaggerConfig {
...
}

//JWT 토큰 검증이 필요한 매서드나 컨트롤러에 아래 어노테이션 추가
@SecurityRequirement(name = "bearerAuth")

 

우측에 자물쇠가 생긴게 JWT 토큰 검증 설정을 한 API이다.

 

controller

API 별 에러처리 코드를 제외하면 딱히 애먹은건 없는 듯하다.

검증도 잘 동작한다.

 

API 별 에러처리 코드 << 이게 사용법, 코드 가독성을 망치는 주범이다. controller 매서드 상단에 이 어노테이션을 붙인다.

@Operation(summary = "Get Test API", description = "This is GET API")
@ApiResponses(value = [
    ApiResponse(responseCode = "200", description = "Success", content = [
        Content(mediaType = "application/json", array = ArraySchema(schema = Schema(implementation = TestResponseDto::class)))
    ]),
    ApiResponse(responseCode = "202", description = "사용하지 않는 파라미터", content = [Content()])
])
@GetMapping("/get-test")
fun getTest(@Valid @ParameterObject getRequestDto: GetRequestDto, bindingResult: BindingResult): TestResponseDto {
.....
}

이게 진짜 문제인게, 202 추가를 하면 200이 사라져서 200까지 같이 설정해야한다. 어이X

 

이 이상 어떻게 더 잘 쓸 수 있을지 모르겠다.

 

검증은 각각의 요청객체에 달아놨는데 잘 동작한다.

data class GetRequestDto(
        @field:Schema(description = "문구 패턴 테스트", pattern = "swagger|openAPI|docs|fox")
        val test: String,
        @field:Schema(description = "버전은 숫자로")
        val version: Int,
        @field:Schema(description = "생략 가능한 변수")
        val notRequired: String?
)

코틀린에서는 @filed를 반드시 붙여줘야 한다.

 

뭐가 틀렸는지 딱 알려준다.

 

 

또, 코틀린에서는 required를 해제할 때는 ? 를 붙여주면 된다.

GitHub

https://github.com/imsosleepy/openapi3-swagger-ui

 

마치며

팀의 규모가 커지고, 관리해야할 사항이 늘어나면 내가 작성한 코드가 기억이 안날 때가 있고 다른 사람의 코드를 봐야할 경우가 점점 늘어날 것이다.

 

Postman이 사용도 편하고 가독성도 좋지만, 아무래도 코드 내에 코드 설명이 있으면 좋지 않을까해서 swagger를 만져봤다.

 

API 별 에러처리 코드에서 포기할뻔했지만, 그리고 결함이 조금 있지만 그래도 내가 생각하는 끝까지 사용해본 것 같다.

 

Go나 Python에서 쓴걸 보면,

 

어노테이션을 많이 써도 코드가 못생겨지진 않던 것 같은데 자바/코틀린만 유독 이상한 것 같기도하다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함