OpenAPI Specification을 이용한 더욱 효과적인 API 문서화

OpenAPI Specification을 이용한 더욱 효과적인 API 문서화

시작하며

안녕하세요. 카카오페이 증권플랫폼파트에서 증권계좌 플랫폼 서버 개발을 하고 있는 제이입니다.

최근 새로운 Spring 기반 서버 프로젝트를 구축하게 되면서 API 문서화 개발을 진행하게 됐습니다. Spring 기반의 프로젝트에서는 Swagger와 Spring REST Docs를 가장 많이 사용하는데요. 각각 장단점이 있어 팀의 선호에 따라 하나를 선택하는 경우가 많습니다.

저희 파트는 테스트케이스 작성을 필수로 하고 있으므로 Spring REST Docs를 이용하기 좋았지만 Swagger의 아름다운 UI와 좋은 사용성도 놓치기는 아쉬웠습니다. 동료들과 의견을 나누던 중 ‘OpenAPI Specification(이하 OAS)‘를 이용한 Swagger와 Spring REST Docs의 장점을 합치는 방법을 알게 되었고 이를 구현한 방법을 공유드리려고 합니다.

API 문서화 도구

Swagger

Swagger는 Controller에 몇 가지의 어노테이션을 달기만 해도 API 문서가 만들어지는 편리함이 있지만 Test를 강제하지는 않기에 문서의 신뢰도를 높게 유지하기 어려운 문제가 있습니다. 그럼에도 문서가 워낙 아름답고 API Test도 지원하기 때문에 많이 사용되고 있습니다.

장점단점
아름다운 문서테스트가 없어 높지 않은 신뢰도
API Test 기능 지원Swagger 어노테이션이 비즈니스 소스코드와 섞임

Swagger 예시 이미지 - Swagger Petstore
Swagger 예시 이미지 - Swagger Petstore

Spring REST Docs

Spring REST Docs는 Integration 테스트를 작성해서 통과해야만 문서가 작성되므로 문서의 신뢰도가 높게 유지된다는 장점이 있습니다. Swagger와 다르게 소스코드에 변경을 주지 않아 비즈니스 로직과 테스트케이스를 분리하는 장점이 있고 테스트를 강제하므로 유지보수에도 도움을 줍니다. 반면 문서가 Swagger에 비해 아름답지 않고 API Test를 지원하지 않는 단점이 있습니다.

장점단점
테스트 강제로 인한 높은 신뢰도아름답지 않은 문서
비즈니스 소스코드에 영향 없음API Test 기능 미지원

Spring REST Docs 예시 이미지
Spring REST Docs 예시 이미지

OpenApi Specification(OAS) 기반 API 문서화

Swagger 팀이 SmartBear Software에 합류하면서 Swagger Spec.이 OpenApi Spec.으로 명칭이 바뀌었고 오늘날에는 RESTful API 스펙에 대한 사실상의 표준으로서 활용되고 있다고 합니다. Swagger-UI는 이 OAS를 해석하여 API 스펙을 시각화해줍니다. 또한 Postman, Paw 같은 API Client들도 OAS를 지원하고 있어 OAS의 활용도가 다양한 것을 알 수 있습니다.

OAS를 생성하는 restdocs-api-spec 오픈소스

Spring REST Docs와 Swagger의 장점을 어떻게 합칠 수 있을까요? 운이 좋게도 독일 기업 epages에서 Spring REST Docs를 연동하여 OAS 파일을 만들어주는 오픈소스(restdocs-api-spec)를 제공하고 있습니다. 이 오픈소스를 이용해서 OAS 파일을 생성하고 Swagger-UI로 띄우면 되는 것입니다.

구현

구현 순서는 다음과 같습니다.

  1. Swagger-UI standalone, Static Routing 세팅
  2. restdocs-api-spec을 이용한 OAS 파일을 생성하는 빌드 환경 구축
  3. 생성된 OAS 파일을 Swagger 디렉터리로 복사하는 스크립트 작성(copyOasToSwagger)
  4. SampleController 코드 작성
  5. MockMvc REST Docs Test 코드 작성

1. Swagger-UI standalone, Static Routing 세팅

Swagger-UI gradle 의존성은 불필요하며 standalone (HTML/CSS/JS) 파일만 있으면 됩니다.

1.1. Swagger UI 정적 파일 설치

Swagger UI 정적 파일 설치 사이트 하단에 있는 ‘Static files without HTTP or HTML’ 부분에서 latest release를 다운 받아 /dist 디렉터리만 복사해줍니다. 저는 resources 디렉터리 하위에 static 디렉터리를 생성하여 복사해두었습니다.

Swagger UI 정적 파일
Swagger UI 정적 파일

1.2. Static Routing 세팅

정적 파일 경로에 알맞게 WebMvcConfigurer의 addResourceHandlers() 메서드를 작성합니다.

@Configuration
class StaticRoutingConfiguration : WebMvcConfigurer {
    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/")
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/static/swagger-ui/")
    }
}
1.3. Swagger file 수정
  • index.html
    • swagger-ui.html으로 이름 변경
    • 내부 js, css 경로를 static routing으로 적용
    • SwaggerUIBundle 경로는 생성될 yaml 파일의 경로로 입력
  • 불필요한 파일 삭제
    • oauth2-redirect.html
    • swagger-ui.js
    • swagger-ui-es-bundle-core.js
    • swagger-ui-es-bundle.js

2. restdocs-api-spec을 이용한 OAS 출력

Spring REST Docs의 extension을 포함하는 restdocs-api-spec과 MockMvc wrapper를 제공하는 restdocs-api-spec-mockmvc를 이용해서 OAS 파일을 생성하고자 합니다. RestAssured도 지원되니 필요하신 분은 restdocs-api-spec-restassured를 사용하셔도 됩니다. build tool은 gradle로 진행하겠습니다.

build.gradle.kts 파일에 아래처럼 plugins와 dependency를 추가해주면 사용할 수 있습니다. 깃헙에 legacy plugin 예시도 함께 제공되니 편한 것을 이용해서 환경을 잡아주면 됩니다. openapi, openapi3, Postman 등 다양한 output format이 제공되고 커스터마이징할 수 있는데 본 포스팅에서는 Swagger 연동을 위해 openapi3 설정을 커스터마이징 해보겠습니다.

OAS 파일 기본 생성 경로는 /build/api-spec 입니다.

plugins {
    id("com.epages.restdocs-api-spec") version "0.15.3"
}

repositories {
    mavenCentral()
}

dependencies {
    testImplementation("com.epages:restdocs-api-spec-mockmvc:0.15.3")
}

openapi3 {
    this.setServer("https://localhost:8080") // list로 넣을 수 있어 각종 환경의 URL을 넣을 수 있음!
    title = "My API"
    description = "My API description"
    version = "0.1.0"
    format = "yaml" // or json
}
  • gradle Tasks - documentation에 openapi, openapi3, Postman Task가 기본으로 추가된 모습을 확인할 수 있습니다.

여기까지만 작업하고 Test 코드 없이 openapi3을 실행하면 /build/api-spec 디렉터리에 OAS 기본 구조가 작성된 openapi3.yaml 파일이 생성됩니다.

# /build/api-spec
openapi: 3.0.1
info:
  title: My API
  description: My API description
  version: 0.1.0
servers:
  - url: http://localhost:8080
tags: []
paths: {}
components:
  schemas: {}

3. 생성된 OAS 파일을 Swagger 디렉터리로 복사하는 스크립트 작성(copyOasToSwagger)

openapi3 Task를 먼저 실행시켜 /build 디렉터리에 OAS 파일을 생성한 후, Swagger 디렉터리로 복사하는 Task를 작성합니다. Task를 실행시키면 test -> openapi3 -> copyOasToSwagger 순으로 Task가 진행되며 Swagger 디렉터리로 파일이 복사되는 것을 확인할 수 있습니다.

// build.gradle.kts
tasks.register<Copy>("copyOasToSwagger") {
    delete("src/main/resources/static/swagger-ui/openapi3.yaml") // 기존 OAS 파일 삭제
    from("$buildDir/api-spec/openapi3.yaml") // 복제할 OAS 파일 지정
    into("src/main/resources/static/swagger-ui/.") // 타겟 디렉터리로 파일 복제
    dependsOn("openapi3") // openapi3 Task가 먼저 실행되도록 설정
}

4. SampleController 코드 작성

Sample 단건을 조회하는 간단한 SampleController API를 작성합니다.

@RequestMapping("/api/v1/samples")
@RestController
class SampleController {
    @GetMapping("{sampleId}")
    fun getSampleById(
        @PathVariable sampleId: String,
    ): SampleResponse =
        SampleResponse(sampleId, "sample-$sampleId")
}

data class SampleResponse(
  val sampleId: String,
  val name: String,
)

5. MockMvc REST Docs Test 코드 작성

MockMvc+MockMvcRestDocumentationWrapper를 이용하여 테스트케이스를 작성하였습니다. Wrapper와 ResourceSnippet을 epages 라이브러리로 대신 사용하는 것 외에는 Spring REST Docs와 대부분 동일하게 코드를 작성할 수 있습니다.

@ExtendWith(RestDocumentationExtension::class, SpringExtension::class)
@SpringBootTest
class SampleControllerTest {
    private lateinit var mockMvc: MockMvc

    @BeforeEach
    internal fun setUp(context: WebApplicationContext, restDocumentation: RestDocumentationContextProvider) {
        mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .apply<DefaultMockMvcBuilder>(MockMvcRestDocumentation.documentationConfiguration(restDocumentation))
            .build()
    }

    @Test
    fun getSampleByIdTest() {
        val sampleId = "aaa"
        mockMvc.perform(
            get("/api/v1/samples/{sampleId}", sampleId)
        )
            .andExpect(status().isOk)
            .andExpect(jsonPath("sampleId", `is`(sampleId)))
            .andExpect(jsonPath("name", `is`("sample-$sampleId")))
            .andDo(
                MockMvcRestDocumentationWrapper.document(
                    identifier = "sample",
                    resourceDetails = ResourceSnippetParametersBuilder()
                        .tag("Sample")
                        .description("Get a sample by id")
                        .pathParameters(
                            parameterWithName("sampleId").description("the sample id"),
                        )
                        .responseFields(
                            fieldWithPath("sampleId").type(JsonFieldType.STRING).description("The sample identifier."),
                            fieldWithPath("name").type(JsonFieldType.STRING).description("The name of sample."),
                        ),
                ),
            )
    }
}
5.1. 기존 Spring REST Docs로 작성한 코드를 활용하려면

Spring의 MockMvcRestDocumentation을 MockMvcRestDocumentationWrapper로 바꿔주면 됩니다.

API 문서화 결과

copyOasToSwagger Task를 실행한 다음 서버를 띄우면 Swagger API 문서가 잘 보이고 API Testing도 잘 동작하는 것을 확인할 수 있습니다.

  • Swagger API 문서
  • 잘 동작하는 API Testing
  • sample API가 추가된 OAS 파일
openapi: 3.0.1
info:
  title: My API
  description: My API description
  version: 0.1.0
servers:
  - url: http://localhost:8080
tags: []
paths:
  /api/v1/samples/{sampleId}:
    get:
      tags:
        - Sample
      summary: Get a sample by id
      description: Get a sample by id
      operationId: sample
      parameters:
        - name: sampleId
          in: path
          description: ''
          required: true
          schema:
            type: string
      responses:
        '200':
          description: '200'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api-v1-samples-sampleId486549215'
              examples:
                sample:
                  value: '{"sampleId":"aaa","name":"sample-aaa"}'
components:
  schemas:
    api-v1-samples-sampleId486549215:
      type: object

마치며

본 포스팅에서는 자주 비교되곤 하는 Spring REST Docs와 Swagger의 장점만을 뽑아낸 API 문서화 구축 방법을 공유드렸습니다. 빌드 시 생성되는 OAS 파일은 Postman, Paw, Insomnia 같은 API Client에서 Import하여 사용할 수 있으므로 API Client를 구성하는 수고를 줄일 수도 있습니다. :) 실무에 도입한지는 1년이 넘었는데 그동안 테스트케이스 작성을 강제하여 API 품질을 항상 높게 유지할 수 있었고 Swagger의 아름다운 UI도 제공하여 다른 조직과의 협업도 우수히 진행할 수 있었습니다.

실무에서는 개발 못지않게 문서화도 중요해지고 있는 요즘입니다. 제가 공유드린 OAS를 활용한 API 문서화 구축이 아니더라도 다양한 도구를 활용해 API 문서화를 진행하여 API 품질 유지는 물론 원활한 협업까지 느껴보시길 바랍니다.

참고사항

  • API 문서화를 자동화시키려면 copyOasToSwagger Task를 먼저 실행시켜서 OAS 파일을 생성한 다음 빌드해야 합니다. 위 내용은 카카오페이 배포 파이프라인에 맞춰 구성한 예제이므로 참고 부탁드립니다.
  • epages의 restdocs-api-spec이 아직 모든 Spring REST Docs의 스펙을 100% 커버하지는 못하므로 깃헙에서 문의글을 남겨야 하는 경우가 있습니다. 물론 핵심 기능은 이상 없이 제공되니 큰 문제는 아닌 듯합니다.

참고문헌

jay.pg
jay.pg

카카오페이 머니코어제휴파티에서 다양한 서버를 개발하고 있는 제이입니다. 금융 산업 전반에 관심이 많아 하루하루 재밌게 일하고 있습니다.