본문 바로가기
프로그래밍/기타

Open API Spefication 활용하기 - 3편 ( Spring Boot 에서 yaml 파일 자동으로 만들기 )

by 사바라다 2022. 9. 12.

안녕하세요 ! 오늘은 Open Api Specification 3번째 시간입니다. 오늘 포스팅할 주제는 Spring Boot에서 Controller Class를 만들고 이렇게 만들어진 Controller Class를 기반으로 자동으로 yaml 파일을 만들어보도록 하겠습니다.

환경

간단하게 서버의 환경을 말씀드리면 아래와 같습니다. 저는 테스트를 kotlin 기반으로 작성했지만 Java로도 충분히 동일한 결과를 얻어내실 수 있을 것입니다.

  • kotlin 1.6.21
  • Spring Boot 2.7.3

build.gradle.kts plugins

spring boot의 Controller 코드를 자동으로 변환하기 위해서는 아래의 plugin이 추가로 필요합니다. 해당 플러그인을 추가해주세요. 1.4.0 버전은 2022년 9월 12일자에서의 최신 버전입니다. 버전은 github_openapi-gradle-plugin에서 확인 가능합니다.

plugins {
    id("org.springdoc.openapi-gradle-plugin") version "1.4.0"
}

샘플 코드

그러면 오늘 샘플이 되어줄 Controller 코드와 Request, Response Model 코드를 살펴보도록 하겠습니다. 해당 코드들은 특별할것이 없는 일반적으로 작성하는 Controller와 Request, Response Model 들입니다.

Controller

Controller 코드입니다. GET /api/open/{id}, GET /api/open, PUT /api/open의 3가지 API Operation을 가지고 있습니다. 그리고 path Parameter, RequestBody, ResponseBody를 경우에 따라서 정의하고 있습니다. Request와 Response 응답값은 더미데이터로 채워두었습니다.

@RestController
@RequestMapping("/api/open")
class OpenApiController(
    private val openApiClientService: OpenApiClientService
) {

    @GetMapping("/{id}")
    fun findById(@PathVariable id: Long): OpenApiResponse {
        return OpenApiResponse(
            string = "string",
            number = 1,
            number2 = id,
            number3 = 3.3,
            time = Instant.now()
        )
    }

    @GetMapping("/")
    fun findResponses(): Collection<OpenApiResponse> {
        return listOf(
            OpenApiResponse(
                string = "string",
                number = 1,
                number2 = 2L,
                number3 = 3.3,
                time = Instant.now()
            ),
            OpenApiResponse(
                string = "string",
                number = 2,
                number2 = 4L,
                number3 = 6.6,
                time = Instant.now()
            )
        )
    }

    @PutMapping("/")
    @ResponseStatus(HttpStatus.OK)
    fun createBook(
        @RequestBody request: OpenApiRequest
    ): OpenApiResponse {
        return OpenApiResponse(
            string = request.string,
            number = request.number,
            number2 = request.number2,
            number3 = request.number3,
            time = request.time
        )
    }
}

Data Transfer Object Model

kotlin 코드로 만든 RequestBody와 ResponseBody의 DTO 모델입니다. kotlin의 data 클래스로 작성하였습니다.

data class OpenApiRequest(
    val string: String,
    val number: Int,
    val number2: Long,
    val number3: Double,
    val time: Instant
)

data class OpenApiResponse(
    val string: String,
    val number: Int,
    val number2: Long,
    val number3: Double,
    val time: Instant
)

gradle task code

위의 Controller와 Model 코드를 기본으로 gradle task를 작성한 후 실행하여 우리가 원하는 Open API Specification(OAS) 파일을 얻어낼 수 있습니다. 파일은 설정하기에 따라서 json 또는 yaml 형식을 얻어낼 수 있습니다. 또한 중요한 것이 이 것은 spring boot의 swagger를 기반으로 하고 있습니다. 그렇기 때문에 runtime 으로 실행을 한 후 해당 파일을 얻어오는 방법을 취하고 있다는 것입니다. 따라서 task를 실행하면 서버를 띄운 후 파일을 얻어내는 과정을 거칩니다. 그렇기 때문에 서버가 정상적으로 뜰때까지 기다려주어야합니다. 기다리는것은 waitTimeInSeconds 설정을 통해서 할 수 있습니다.

openApi { // json 파일을 얻어냄
    apiDocsUrl.set("http://localhost:8080/v3/api-docs")
    outputDir.set(file("$rootDir/docs"))
    outputFileName.set("swagger.json")
    waitTimeInSeconds.set(5)
}

openApi { // yaml 파일을 얻어냄
    apiDocsUrl.set("http://localhost:8080/v3/api-docs.yaml")
    outputDir.set(file("$rootDir/docs"))
    outputFileName.set("swagger.yaml")
    waitTimeInSeconds.set(5)
}

위 처럼 설정을 한 후 generateOpenApiDocs task를 gradle에서 실행하면(gradle generateOpenApiDocs) 설정한 outputDir에 OAS 파일이 생성되게 됩니다.

추가로 위의 세팅 말고도 파일을 그룹에 따라 분리시킬 수 있는 groupedApiMappings와 JAVA_ENV를 설정하는 customBootRun 등의 세팅이 있습니다. 자세한 내용은 문서를 참고하시면 좋을것 같습니다.

task 실행 결과

Json 파일과 yaml 파일을 각각 생성한 결과는 아래와 같습니다. 더보기를 누르시면 결과를 확인하실 수 있습니다.

아래는 Json 파일입니다.

더보기
{
  "openapi": "3.0.1",
  "info": {
    "title": "OpenAPI definition",
    "version": "v0"
  },
  "servers": [
    {
      "url": "http://localhost:8081",
      "description": "Generated server url"
    }
  ],
  "paths": {
    "/api/open/": {
      "get": {
        "tags": [
          "open-api-controller"
        ],
        "operationId": "findResponses",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OpenApiResponse"
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "open-api-controller"
        ],
        "operationId": "createBook",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenApiRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/open/{id}": {
      "get": {
        "tags": [
          "open-api-controller"
        ],
        "operationId": "findById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/open/test": {
      "get": {
        "tags": [
          "open-api-controller"
        ],
        "operationId": "getTest",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "OpenApiRequest": {
        "type": "object",
        "properties": {
          "string": {
            "type": "string"
          },
          "number": {
            "type": "integer",
            "format": "int32"
          },
          "number2": {
            "type": "integer",
            "format": "int64"
          },
          "number3": {
            "type": "number",
            "format": "double"
          },
          "time": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OpenApiResponse": {
        "type": "object",
        "properties": {
          "string": {
            "type": "string"
          },
          "number": {
            "type": "integer",
            "format": "int32"
          },
          "number2": {
            "type": "integer",
            "format": "int64"
          },
          "number3": {
            "type": "number",
            "format": "double"
          },
          "time": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}

 

아래는 yaml 파일입니다.

더보기
openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost:8080
  description: Generated server url
paths:
  /api/open/:
    get:
      tags:
      - open-api-controller
      operationId: findResponses
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/OpenApiResponse'
    put:
      tags:
      - open-api-controller
      operationId: createBook
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenApiRequest'
        required: true
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/OpenApiResponse'
  /api/open/{id}:
    get:
      tags:
      - open-api-controller
      operationId: findById
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
          format: int64
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/OpenApiResponse'
components:
  schemas:
    OpenApiRequest:
      type: object
      properties:
        string:
          type: string
        number:
          type: integer
          format: int32
        number2:
          type: integer
          format: int64
        number3:
          type: number
          format: double
        time:
          type: string
          format: date-time
    OpenApiResponse:
      type: object
      properties:
        string:
          type: string
        number:
          type: integer
          format: int32
        number2:
          type: integer
          format: int64
        number3:
          type: number
          format: double
        time:
          type: string
          format: date-time

마무리

오늘은 Open API Specification 3편으로 Spring Boot Server 코드를 이용해서 자동으로 OAS File을 만들어 내는 것을 확인해 보았습니다.

다음 시간에는 이렇게 만들어진 Open API Specification 파일, yaml 파일을 토대로 자동으로 client에서 사용할 retrofit2 기반의 kotlin 코드를 만들어보도록 하겠습니다.

감사합니다.

참조

[1] https://github.com/springdoc/springdoc-openapi-gradle-plugin

[2] https://springdoc.org/plugins.html

댓글