この記事は Voicy Advent Calendar 2020 1日目の記事です。
初めまして、Voicyのサーバーサイドエンジニアをやっているカワムラです。
今回は普段の開発でもよくやっているswagger/OpenAPI(以下swagger)のファイルからスタブサーバーを自動生成して開発を効率化させる方法をご紹介したいと思います。
背景
新しくHTTPのAPIサーバーを新規開発する際、Swaggerを使用する場合の流れは以下のようなものが一般的かと思います。
1.バックエンド・フロントで相談しながらSwaggerでAPIの仕様を定義する
↓
2.バックエンド側がエンドポイントを一つずつ開発
↓
3.バックエンドの開発が終わったエンドポイントに合わせてフロントが開発
しかし、この方法だと同時にフロントとバックエンド開発を始めることができないし、フロントの開発がバックエンドのエンドポイント開発の順序に左右されてしまうというデメリットがあります。
それによって開発が非効率になってしまったり、バックエンドの開発スピードが全体のボトルネックになってしまうなどの問題が生じてしまう可能性があります。
そこで、今回はswaggerでAPIの定義さえしてしまえば指定したレスポンスを返してくれるスタブサーバーのCircleCIを用いた自動生成方法をご紹介します。
導入に使用したライブラリ
今回使用したライブラリは以下になります。
swagger-codegen
https://github.com/swagger-api/swagger-codegen
api-spec-converter
https://github.com/LucyBot-Inc/api-spec-converter
導入手順
※アプリケーションはEKSで管理しておりGitOpsで運用している前提で、ビルドしたDockerイメージをECRにプッシュするまでの手順を紹介します。
swaggerファイルを作成する
まず初めにAPIのSwaggerファイルを作成します。swagger/openapiのバージョンは2でも3でも問題はありません。今回はopenapiの3系を使いました。
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification
termsOfService: http://swagger.io/terms/
contact:
name: Swagger API Team
email: apiteam@swagger.io
url: http://swagger.io
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
servers:
- url: http://petstore.swagger.io/api
paths:
/pets:
get:
description: |
Returns all pets from the system that the user has access to
Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.
operationId: findPets
parameters:
- name: tags
in: query
description: tags to filter by
required: false
style: form
schema:
type: array
items:
type: string
- name: limit
in: query
description: maximum number of results to return
required: false
schema:
type: integer
format: int32
responses:
'200':
description: pet response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
description: Creates a new pet in the store. Duplicates are allowed
operationId: addPet
requestBody:
description: Pet to add to the store
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewPet'
responses:
'200':
description: pet response
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
......もっと続く
swaggerの定義に関して詳しく記載しませんが、スタブサーバーを作成した際に指定したレスポンスを返すようにするには、swaggerファイルに以下のように値を記載する必要があります。
responses:
200:
description: success
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Pet"
example:
- id: 1
name: 花子
- id: 2
name: 太郎
- id: 3
name: ぽち
ここのexampleに記載することでswaggerファイルからサーバーを生成した際に、レスポンスとして返すことが可能になります。
また、スキーマで定義した部分でもexampleを設定することによってレスポンスとして返すことが可能です。
components:
schemas:
Pet:
allOf:
- $ref: '#/components/schemas/NewPet'
- type: object
required:
- id
properties:
id:
type: integer
format: int64
example: 1
name:
type: string
example: シロ
swaggerファイルからサーバーを自動生成する
ここからの作業は全てCircle CIで自動化してしまいます。以下が.circleci/config.yamlになります。
全体としてはこちらになりますが、下で部分ごとに分けて説明していきます。
version: 2.1
orbs:
node: circleci/node@3.0.0
aws-ecr: circleci/aws-ecr@6.9.0
jobs:
downgrade-swagger-config:
executor: node/default
steps:
- checkout
- run: sudo npm install -g api-spec-converter
- run: api-spec-converter --from=openapi_3 --to=swagger_2 --syntax=yaml swagger/swagger.yaml > swagger2.yaml
- persist_to_workspace:
root: .
paths:
- .
create-stub-server:
build:
machine: true
steps:
- attach_workspace:
at: .
- checkout
- run: docker run --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli generate -i /local/swagger2.yaml -l nodejs-server -o /local
- persist_to_workspace:
root: .
paths:
- .
deploy:
executor: aws-ecr/default
steps:
- attach_workspace:
at: .
- aws-ecr/build-and-push-image:
dockerfile: build/stub.Dockerfile
repo: stub-sample-api
tag: "${CIRCLE_SHA1}"
workflows:
version: 2
deploy-stub-cluster:
jobs:
- downgrade-swagger-config:
filters:
branches:
only: dev
- create-stub-server:
requires:
- downgrade-swagger-config
- deploy:
name: Deploy Stub to DEV Image
context: dev
requires:
- create-stub-server
orbs:
node: circleci/node@3.0.0
aws-ecr: circleci/aws-ecr@6.9.0
ここでは、今回使用するOrbを定義しています。
OrbとはCircleCIのcommandsやjobs,executors をパッケージとして使い回すことのできる仕組みです。
詳しくは[こちら]
(https://circleci.com/docs/ja/2.0/orb-intro/)
jobs:
downgrade-swagger-config:
executor: node/default
steps:
- checkout
- run: sudo npm install -g api-spec-converter
- run: api-spec-converter --from=openapi_3 --to=swagger_2 --syntax=yaml swagger/swagger.yaml > swagger2.yaml
- persist_to_workspace:
root: .
paths:
- .
create-stub-server:
build:
machine: true
steps:
- attach_workspace:
at: .
- checkout
- run: docker run --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli generate -i /local/swagger2.yaml -l nodejs-server -o /local
- persist_to_workspace:
root: .
paths:
- .
deploy:
executor: aws-ecr/default
steps:
- attach_workspace:
at: .
- aws-ecr/build-and-push-image:
dockerfile: build/stub.Dockerfile
repo: stub-sample-api
tag: "${CIRCLE_SHA1}"
ここでは3つのJobを定義しています。
まず1つ目の downgrade-swagger-configは、定義したOpenAPI3.0の定義ファイルをswagger2.0にダウングレードさせています。理由としてはOpenAPI3.0からスタブサーバーを生成した場合に、exampleで指定した値がレスポンスとして返されないという仕様だったため、一旦2.0にダウングレードする手順を挟みました。したがって最初からswagger2.0で定義されている場合は必要ありません。
余談ですが、こちらのツールはswagger2.0→openAPI3.0への変換などもコマンドで簡単に実行できるのでおすすめです。
https://github.com/LucyBot-Inc/api-spec-converter
2つ目のcreate-stub-serverは実際にswagger-codegen-cliを用いてサーバーを自動生成する部分です。
ここではDockerHubに上がっているswagger-codegenのイメージを用いて生成しています。
https://hub.docker.com/r/swaggerapi/swagger-codegen-cli
-lで様々な言語でのサーバーの生成が可能なのですが、今回はnodejsを用いています。(普段はGo言語を用いているがGo言語で自動生成されるサーバーの精度が低かったため)
このツールを使って自動生成できるサーバーの言語は以下です。たくさんありますが、言語ごとに生成されるサーバーの精度が異なるため、注意してご利用ください。
Ada, C# (ASP.NET Core, NancyFx), C++ (Pistache, Restbed), Erlang, Go, Haskell (Servant), Java (MSF4J, Spring, Undertow, JAX-RS: CDI, CXF, Inflector, RestEasy, Play Framework, PKMST), Kotlin, PHP (Lumen, Slim, Silex, Symfony, Zend Expressive), Python (Flask), NodeJS, Ruby (Sinatra, Rails5), Rust (rust-server), Scala (Finch, Lagom, Scalatra)
そして最後がdeployです。先ほど生成したサーバーをDockerで実行して、そのImageをECRにデプロイします。orbsを用いることで非常に簡素にECRデプロイを書くことが可能です。
また、Dockerfileに関しては以下のように簡潔なものになっています。
FROM node:12
WORKDIR /app
ADD . /app
RUN npm install
RUN npm audit fix
COPY . .
EXPOSE 8080
CMD [ "node", "index.js" ]
workflows:
version: 2
deploy-stub-cluster:
jobs:
- downgrade-swagger-config:
filters:
branches:
only: dev
- create-stub-server:
requires:
- downgrade-swagger-config
- deploy:
name: Deploy Stub to DEV Image
context: dev
requires:
- create-stub-server
workflowに関しては先ほど記載したjobを上から順に行っている形で定義しています。
これによってSwaggerを定義→devブランチにマージをすることでサーバーが自動生成されるため、APIの定義さえしてしまえばバックエンド・フロントで並行開発がよりスムーズなるかと思います!
以上がSwaggerの設定ファイルからスタブサーバーを自動生成する方法になります。
最後までお読みいただきありがとうございました!!!