Swagger UI × Amazon EC2 × Dockerで開発初期からAPIの繋ぎ込みを意識できる環境を構築してみた

Wataru SendoPosted by

こんにちは!
PHP × Unityを使った新規開発プロジェクトでサーバサイドエンジニアをしている仙道です。

今回の記事ではSwagger UIとAmazon EC2、Dockerを使って開発初期から繋ぎこみを意識した開発を行うための取り組みを紹介したいと思います。

今回の記事ではAmazon EC2やDockerの基本的な使い方については触れないため、もしこれらについて分からないという場合は、下記のドキュメント等を参照いただければと思います。

Swagger UIとは?

Swagger UIとは、Swaggerが提供するツールの一つでSwagger SpecというYAMLまたはJSONで記述されたAPIの設計図からWeb上で閲覧できるAPIドキュメントを生成してくれるものです。
生成されたドキュメントでは、エンドポイントやリクエストパラメータ、レスポンスの内容といったAPI定義を閲覧することができます。
また、あくまでサンプルの値でのやりとりではありますが、APIを叩くとレスポンスを返してくれるAPIモック機能も提供されています。

使用感などについては、オンラインデモ(http://petstore.swagger.io/?_ga=2.243286406.2080197499.1506933975-167945699.1505133057)が提供されているため、そちらを参照いただければと思います。

開発初期からAPIの繋ぎ込みを意識した開発がしたい!

アプリやゲーム開発を行う際、サーバ / クライアントそれぞれの中身が出来てから初めてAPIの繋ぎ込みを行ってしまうと想定外の後戻りが発生するリスクが大きいです。
そのため、あらかじめサーバ / クライアントでAPI定義のすり合わせを行い、ドキュメントやAPIモックサーバを用意しておくと上記のようなリスクを減らせるのではないかと思います。

私の所属しているプロジェクトでは、各機能ごとにサーバ / クライアントエンジニアを割り当て、常にいくつかの機能を並走して開発するスタイルを取っています。
機能によってはAPI定義がこれからで流動性が高かったり、また別の機能では開発終盤でAPI定義にはほとんど手が加えられない状態であるなど、各機能でSwagger Specファイル(APIの設計図)を修正する頻度がまちまちです。
こうした環境で、単純に1つのSwagger Specファイル、1つのSwagger UI(ドキュメント)という状態で開発を行うのは、Swagger Specファイルのマージ作業が大変になってしまったり、Swagger UIでの構造の変更などが煩わしくなってしまいます。
そこで、各機能ごとにSwagger Specファイルの変更とSwagger UIの参照ができ、Swagger Specファイルの更新を行ったタイミングでSwagger UIの更新が自動的に行われるような環境が作れないかと考えました。

APIモックサーバの構成図

実際に構築したAPIモックサーバ環境の構成図が上図になります。
今回は例として、SampleAという機能とSampleBという2つの機能を開発している想定で上図に反映させています。

動作の流れとして、まず、エンジニアがGitHubのSwagger用リポジトリに機能ごとに分割されたSwagger Specファイルをpushします。
上図のEC2インスタンスAでは、Jenkinsが稼働していてGitHubリポジトリの更新をポーリングによって監視しています。更新を検知すると、上図のEC2インスタンスBで稼働しているSwagger UIとAPIモックサーバを最新にするためのジョブが実行されます。このジョブが完了するとEC2インスタンスBで動作しているSwagger UI Dockerイメージを載せたDockerコンテナとAPIモックサーバの役割を果たすDockerコンテナが最新のSwagger Specファイルを反映した状態になります。

本構成では、一つの機能でSwagger UIとAPIモックサーバそれぞれでdockerコンテナを立ち上げるようになっています。
後述しますが、APIモックサーバはswagger-codegen-cliというdockerイメージを使用してNode.jsのコードを生成して立ち上げています。
この方法を使用すると、生成されるドキュメントが古いスタイルのものになってしまい視認性が悪いため、新しいスタイルでのドキュメント参照用としてSwagger UI dockerコンテナを別個立ち上げるようにしました。

以降では、構成図の内容を次の2つに分けてそれぞれの構築や設定方法について解説したいと思います。
・ JenkinsによるGitHubリポジトリの監視と更新に合わせたジョブ実行
・ Swagger UIコンテナとAPIモックサーバコンテナの更新

JenkinsによるGitHubリポジトリの監視と更新に合わせたジョブ実行

GitHubリポジトリの監視と更新に合わせてSwagger UIとAPIモックサーバを最新の状態にするためにJenkinsのビルドトリガーを活用することにしました。
ビルドトリガーとは、Jenkinsのジョブの実行をある特定の動作に合わせて行うための設定です。例えば、ある一定の時間ごとにジョブを実行したり、バージョン管理システムの状態をポーリングで監視して変更があった時にジョブを実行するといったことができるようになります。
今回はSwagger Specを管理しているGitリポジトリの更新をトリガーにしたいため、下記のように設定しました。

また、実行するシェルは下記のように設定しました。

実際にSwagger UIコンテナとAPIモックサーバコンテナの更新を行うのはrestart_swagger.shというシェルスクリプトになります。
restart_swagger.shで行っている処理については次節で触れていきます。

Swagger UIコンテナとAPIモックサーバコンテナの更新

Swagger UIコンテナとAPIモックサーバコンテナの更新を行うrestart_swagger.shの内容は以下の通りになっています。

#!/bin/sh
if [ "$1" = "sampleA" ]; then
 uiName=swagger-ui-sampleA
 uiPort=81
 mockName=swagger-mock-sampleA
 mockPort=3001
 specFileName=sampleA.yaml
elif [ "$1" = "sampleB" ]; then
 uiName=swagger-ui-sampleB
 uiPort=82
 mockName=swagger-mock-sampleB
 mockPort=3002
 specFileName=sampleB.yaml
else
 uiName=swagger-ui
 uiPort=80
 mockName=swagger-mock
 mockPort=3000
 specFileName=test.yaml
fi

docker stop ${uiName}
docker stop ${mockName}
docker pull swaggerapi/swagger-ui:v2.2.9
docker pull swaggerapi/swagger-codegen-cli

# swagger-ui run
docker run --name ${uiName} --rm -p ${uiPort}:8080 -e "SWAGGER_JSON=/swagger/${specFileName}" -v ${PWD}:/swagger -d swaggerapi/swagger-ui

# generate mock
docker run -u `id -u`:`id -g` --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli generate -i "/local/${specFileName}" -l nodejs-server -o /local/out/nodejs
cp nodejs_server/Dockerfile out/nodejs/
cd out/nodejs/
sed -i -e "29i // add bugfix" index.js
sed -i -e "30i app.use((req, res, next) => { if (req.headers['content-length'] === '0' && req.headers['content-type'] == null) { req.headers['content-type'] = 'application/json' } next()});" index.js
docker build -t sample-swagger-nodejs .

# mock run
docker run --name ${mockName} --rm -p ${mockPort}:3000 -d sample-swagger-nodejs

3~20行目では、以降の処理でdockerコンテナを立ち上げる際のオプションを機能(今回はSampleA機能とSampleB機能)に応じて決定しています。

22~25行目では、すでに動作しているdockerコンテナを一度止め、swagger-uiというdockerイメージとswagger-codegen-cliというdockerイメージの取得を行っています。
swagger-codegen-cliイメージは、Swagger Specファイルから様々な言語のコードを生成するために使用するdockerイメージです。

28行目では、swagger-ui dockerイメージを基にしたdockerコンテナを立ち上げています。
今回は、機能ごとに用意したSwagger Specファイルを使用したいため、-eオプションを使って環境変数にそれぞれのSwagger Specファイル名を設定しています。

31行目では、swagger-codegen-cli dockerイメージを使ってAPIモックサーバとして動作するNode.jsコードを生成しています。

32行目では、生成したAPIモックサーバ用のNode.jsコードをdockerイメージとしてビルドするため、Node.jsコードを出力したディレクトリへDockerfileのコピーを行っています。

33~35行目では、先ほど生成されたNode.jsコードにおいてリクエストがPOSTかつbodyが空であった場合にエラーになってしまう問題が起きたため、回避するためのコードを追記する処理を行っています。

36行目では、APIモックサーバ用のNode.jsコードを基に、sample-swagger-nodejsという名前でdockerイメージとしてビルドしています。

39行目では、先ほど作成したsample-swagger-nodejsというdockerイメージからdockerコンテナを立ち上げています。

36行目で使用したDockerFileの内容は下記の通りです。

FROM node:alpine
COPY . /src
WORKDIR /src
RUN npm install --production
CMD ["npm", "run", "start:server"]

運用してみてのメリット

現在、私が所属している新規開発プロジェクトで4つのそれぞれ開発フェーズの異なる機能で本環境を運用してみています。
実際に運用してみると、Swagger Specがそれぞれの機能で独立しているため一切のマージ作業が無くなり、開発担当者の判断で変更ができるため非常に楽です。
また、Swagger UIやAPIモックサーバの更新のためのデプロイも開発担当者たちの意図したタイミングで起きるため、ブラウザ更新をしていない間にドキュメントに変更がかかり古い内容を参照しながら開発をしてしまったといったような事故もかなり少なくなったと感じています。

おわりに

今回の記事では、開発初期からAPIの繋ぎ込みを意識した開発を行うために、Swagger UIを活用する取り組みについて紹介しました。

今回、私自身がAWSやDockerについてまだまだ初心者でかなり手探りな構築になってしまいましたが、今回の内容をきっかけにして、もっといい方法を思いついたらまたぜひ共有したいと考えています!

それでは!