OpenAPI 3 ファーストな Web アプリケーション開発(環境編)
ソフトウェアエンジニアの花岡です。今回は OpenAPI 3 ファーストな Web アプリケーション開発における以下のような弊社の環境について書いてみようと思います。
- API ドキュメントの捉え方
- OpenAPI 3 ドキュメントの最もシンプルな分割方法
- OpenAPI 3 ドキュメントの CD と Slack 通知
API ドキュメントの捉え方
弊社では、これまで Developers Blog で何度か触れてきたように、Web API ドキュメントとして OpenAPI を利用してきています。
API ドキュメントの捉え方については Falcon の FAQ を引用したいと思います。
When it comes to API documentation, some developers prefer to use the API implementation as the user contract or source of truth (taking an implementation-first approach), while other developers prefer to use the API spec itself as the contract, implementing and testing the API against that spec (taking a design-first approach).
つまり
- 実装ファースト: API 実装をユーザ契約として、その実装から API ドキュメントを生成する
- 設計ファースト: API 仕様をユーザ契約として、その仕様を満たすように API を実装してテストする
の 2 種類に分類できます。ただし、実装ファーストといっても実装の詳細が契約になるわけではなく、Python でいうと marshmallow のような schema ライブラリで実装した schema などが契約になるという意味です。
現在私の参加しているプロジェクトでは後者の設計ファーストアプローチをとっています。このアプローチの一番のメリットは、API の実装をしなくてもフロントエンドエンジニアが OpenAPI ドキュメントを書いて開発を進めることができる点だと思っています。
openapi-generator などを使えば、API は OpenAPI ドキュメントからスタブ実装を生成することができ、フロントエンドの開発はそれで確認しながら進めることもできます。
また API を変更する場合、サーバサイドを知らなくても OpenAPI ドキュメントを変更するだけで(フロントエンドとしては)OK なのもメリットと思います。
一方でデメリットのうちのひとつは OpenAPI の仕様を理解して API ドキュメントを書いて、それをメンテナンスすることではないでしょうか? API の規模が大きくなると、1 つのファイルに書いていくことは大変で、分割する方法はいくつか Web で見つけることができます。
OpenAPI 3 ドキュメントの最もシンプルな分割方法
今回紹介する方法は、OpenAPI 3 ドキュメントのツリー構造をそのままディレクトリ構造にマッピングするというものです。つまり
.
|-- base.yaml
|-- build.sh
|-- components
| |-- parameters
| | `-- pet_id.yaml
| `-- schemas
| |-- Pet.yaml
| |-- PetNew.yaml
| `-- PetUpdate.yaml
`-- paths
`-- v1
|-- pets
| `-- {pet_id}.yaml
`-- pets.yaml
のようなディレクトリ構成に分割します。base.yaml は
openapi: 3.0.2
info:
title: Example API
version: 0.1.0
のようになっています。openapi.yaml をビルドするには、components と paths 以下のファイルに対して、相対ファイル名から .yaml
を削除しインデントを調整しながら base.yaml に結合します。以下のようななかんじです。コードをきれいに(リライト)してくれる方を募集しています…
#!/bin/sh
#
# Usage: ./build.sh > openapi.yaml
#
here=$(dirname $0)
echo '# This file was auto-generated by build.sh.'
cat $here/base.yaml
(
cd $here/paths
echo 'paths:'
for file in $(find . -name '*.yaml' | sort); do
echo " ${file:1:${#file}-6}:"
sed 's/^/ /' $file
done
)
(
cd $here/components
echo 'components:'
for component in $(ls); do
(
cd $component
echo " $component:"
for file in $(ls *.yaml); do
echo " ${file::${#file}-5}:"
sed 's/^/ /' $file
done
)
done
)
この方法のメリットは
- 分割方法として最もシンプル
- ディレクトリのツリー表示で API の path のツリー構造が把握できる
- RESTful のリソース = OpenAPI の Path Item Object = ファイルがわかりやすい
- OpenAPI 3 の Map 型に対してキーがファイル名になるため何も考えなくても一意になる
- ファイルの移動で path が変わるのが楽しい
などがあります。
デメリットとしては個々のファイルで validate できないことです。そのため編集時には以下のように .yaml
ファイルを watch して build してビルド結果の openapi.yaml を validate するようにしています。
{
"scripts": {
"build": "./build.sh > openapi.yaml && swagger-cli validate openapi.yaml",
"watch": "chokidar '**/*.yaml' -i openapi.yaml -c 'npm run build'"
},
"devDependencies": {
"chokidar-cli": "^1.2.1",
"swagger-cli": "^2.2.0"
}
}
間違ってもすぐにフィードバックが得られる状態です。
OpenAPI 3 ドキュメントの CD と Slack 通知
OpenAPI 3 ドキュメントの Web インタフェースとしては Swagger UI を使うのが簡単で openapi.yaml と同じディレクトリに以下のような index.html を配置して python3 -m http.server
とすると localhost:8000 でいいかんじに OpenAPI 3 ドキュメントを確認することができます。開発時にローカルで確認するのに便利です。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>API docs</title>
<link rel="stylesheet" href="//unpkg.com/swagger-ui-dist@3/swagger-ui.css">
</head>
<body>
<main id="swagger-ui"></main>
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-standalone-preset.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function() {
window.ui = SwaggerUIBundle({
url: '/openapi.yaml',
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset,
],
layout: 'StandaloneLayout',
});
});
</script>
</body>
</html>
これを Google Cloud Build で App Engine の api-docs サービスとして CD してみます。
app.yaml は以下のようになります。
runtime: python27
api_version: 1
threadsafe: true
service: api-docs
handlers:
- url: /?$
static_files: index.html
upload: index\.html
login: admin
secure: always
- url: /openapi\.yaml
static_files: openapi.yaml
upload: openapi\.yaml
login: admin
secure: always
skip_files:
- ^(.*/)?#.*#$
- ^(.*/)?.*~$
- ^(.*/)?\..*$
- ^node_modules/
cloudbuild.yaml は以下のようになります。ここではブランチ名をもとにサービスのバージョンを決めています。
steps:
- name: gcr.io/cloud-builders/gcloud
entrypoint: bash
args:
- -c
- |
set -ex
version=$(echo $BRANCH_NAME | tr [:upper:]. [:lower:]-)
url="https://${version}-dot-api-docs-dot-$PROJECT_ID.appspot.com"
gcloud app deploy app.yaml --no-promote --version=$version --project=$PROJECT_ID --quiet
echo "{\"gcb-tools.link\": {\"name\": \"API docs\", \"url\": \"${url}\"}}" > $$BUILDER_OUTPUT/output
さらにデプロイした api-docs の URL を JSON 形式で $BUILDER_OUTPUT/output
に出力しています。こうすることでビルド結果の Build.results.buildStepOutputs にステップごとの出力が含まれます(参考)。
https://cloud.google.com/cloud-build/docs/configure-third-party-notifications#slack_notifications をベースに Build.results.buildStepOutputs からリンク情報を取り出すと、こんなかんじに Slack 通知されます。
さいごに
OpenAPI 3 ファーストな Web アプリケーション開発に関する弊社の環境について書いてみました。次回は Python による API 実装などについて書きたいと思っています。
弊社では柔軟なこだわりのあるエンジニアのかたを募集しています。
その他の記事
Other Articles
関連職種
Recruit