背景
実装とAPIドキュメントはしばしば乖離して負債になりがちです。なので
といった方針をとることが多いです。
FastAPIは前者のパターンで、実装から自動でOpenAPI Specのドキュメントを生成できます。 後者は以前紹介したこともあります。
OpenAPI で REST API のスキーマ作成 - Carpe Diem
今回は多数のFastAPIベースのマイクロサービスをまとめたモノリポ構成において、ドキュメントを1つにまとめたいと思います。
環境
- FastAPI v0.76.0
- uvicorn v0.17.6
- swagger-ui v4.11.0
モノリポディレクトリ構成
以下のようなディレクトリ構成だとします。service2, service3の中身はservice1と同様とします。
monorepo-documents . ├── LICENSE ├── README.md └── python └── services ├── service1 │ ├── README.rst │ ├── poetry.lock │ ├── pyproject.toml │ ├── service1 │ │ ├── __init__.py │ │ ├── config.py │ │ └── main.py │ └── tests │ ├── __init__.py │ └── test_service1.py ├── service2 └── service3
方法
今回の要件は以下とします。
- 1つ1つのサービスで見ることができるドキュメントを1つのSwaggerUIで管理
- マイクロサービス毎に/docsでドキュメントは見ることができるが、LBへのpathの追加といった運用は避ける
- Dockerで管理
完成イメージとしては
このようにプルダウンで切り替えられるようにします。
方針としては以下でやります。
- 1つ1つのサービスでopenapi.jsonを吐き出す
- 1つのSwaggerUIに↑を保持させ、それぞれ切り替えて参照できるようにする
1つ1つのサービスでopenapi.jsonを吐き出す
docs.pyの用意
FastAPIはドキュメントを一部カスタマイズすることができます。
それを用いながらpythonスクリプトを実行した時にjsonファイルを生成するよう、以下のdocs.pyを配置します。
import json from fastapi.openapi.utils import get_openapi from main import app from config import settings def custom_openapi(): if app.openapi_schema: return app.openapi_schema openapi_schema = get_openapi( title=settings.service_name, version=settings.version, routes=app.routes, ) app.openapi_schema = openapi_schema return app.openapi_schema if __name__ == "__main__": with open(settings.service_name+"-docs.json", "w") as fd: print(json.dumps(custom_openapi()), file=fd)
シェルスクリプト、Makefileの用意
リポジトリルートから一括で生成できるようにします。
シェルスクリプト
サービス毎のディレクトリ構成に規則性を持たせることでスクリプトで扱いやすくします。
#!/bin/bash set -eux ROOT_DIR=`echo ${PWD}` DOCS_DIR=$ROOT_DIR/docs DIR=`echo $1 | awk -F 'docs.py' '{print $1}'` TARGET=`echo $1 | awk -F '/' '{print $(NF-2) "-docs.json"}'` cd $DIR poetry install poetry run python docs.py mv $TARGET $DOCS_DIR
Makefile
Makefileで管理することで
DOCS_LIST := $(shell find . -name "docs.py") DOCS_TARGETS := $(addsuffix .docs,$(DOCS_LIST)) docs: $(DOCS_TARGETS) %.docs: ./scripts/docs.sh $@
以下のように-j
オプションを使って並列実行することが可能です。
$ make -j 3 docs
1つのSwaggerUIに↑を保持させ、それぞれ切り替えて参照できるようにする
SwaggerUIはurls
を設定することで複数のopenapi.jsonを管理できます。
{ "urls": [ { "name": "Master API", "url": "specs/master.yaml" }, { "name": "Bot API", "url": "specs/bot.yaml" } ] }
ref: swagger-ui/configuration.md at master · swagger-api/swagger-ui · GitHub
これを設定するとSwaggerで通常URLを指定する箇所がプルダウンに変換されます。
jqを使ってconfig jsonの生成
これを利用して先程生成した個別のxxx-docs.json
を参照できるようなconfig jsonをjqを使って生成します。
#!/bin/bash set -eux cd docs for file in $( ls *-docs.json ); do URL="./api/$file" jq -n --arg file ${file} --arg url ${URL} '{"urls":[{"url": $url, "name": $file}]}' > tmp-sc-$file.json done jq -s '.[0].urls=([.[].urls]|add)|.[0]' tmp-sc-* > swagger-config.json rm tmp-sc-*
実行してみると以下のファイルが生成できます。
{ "urls": [ { "url": "./api/service1-docs.json", "name": "service1-docs.json" }, { "url": "./api/service2-docs.json", "name": "service2-docs.json" }, { "url": "./api/service3-docs.json", "name": "service3-docs.json" } ] }
Dockerfileの用意
swagger-uiのdocker imageにjsonファイルだけ転送します。
FROM swaggerapi/swagger-ui COPY *.json /usr/share/nginx/html/api/
動作確認
docker imageが完成したら起動してみます。
すると要件を満たしたSwaggerUIが起動できました。
その他
サンプルコード
今回のサンプルコードはこちら
CONFIG_URL
にyamlファイルは使えないのか
どうやら現状はjsonファイルしかサポートしていないようです。
Just tested, seems like documentation is really outdated. swagger-config.yaml is not supported for now, only swagger-config.json
urls option from custom swagger-config.yaml ignored. · Issue #6019 · swagger-api/swagger-ui · GitHub
まとめ
やや泥臭い感じになりましたが、一度仕組み化すれば特にメンテせずとも自動的にドキュメントを1つに集約して管理できるようになりました。