ケイ
2023.03.15
1878
ケイです。
突然ですが皆さん、RestAPIを開発する際はどのように仕様書を管理しているでしょうか?
色々な方法があるかと思いますが、僕はOpenAPIを好んで採用しています。
細かい仕様まで書き込めて、それをコードベースで管理できる点は有難いです。
レンダリングUIも色々選択肢があり、Swaggerを使えばモックとして使えてしまうのも重宝されるポイントではないでしょうか?
今回はそんなOpenAPIを書いていく中で、僕がよく使う方法の一つをご紹介します。
はい、いきなり愚痴が出てしまいました。
そうなんです、OpenAPIをゼロから書く時って割とファイル量が多く、着手から10分後くらいには途方もなさを感じてメンブレ必至です。
既存APIの実装からOpenAPI形式で仕様を書き出すツール等ありますが、初期整備として仕様書を書く際はゼロから書かなければいけないケースが大半かと思います。
前述していますが、OpenAPIは細かい仕様を書くことができる。
だからこそ、完成度の高い仕様書を作成するとなると必然的にファイル量は増えてしまいます。
何かしらの仕様に変更や誤りがあったり、単純に誤字していることに気が付いたり。
いざ実装を進めていると、細かいことにも気が付くようになり、多少なり仕様書を書き換えることがあります。
「仕様書を見ていつつも、書き換えもしたい」
そんなニーズです。
これはOpenAPIの強みの一つなので、僕にとってファイル分割は必ず採用したいものです。
(というより、OpenAPIを一つのファイルで書ききるのは鬼の所業…。)
そして、分割されたファイルを一つにバンドルしておきたい。
一つのファイルにまとまっていると、何かと便利です。
例えば、
$ref
はSwaggerだと上手くいくが、Redocだと上手く参照できずエラーになる。みたいなケース。
(使っているツールに依る部分もあるかと思います。)
こんなケースをカバーするためにも、一つのファイルになっていると恩恵を受けることがあります。
前置きが長くなってしまいました。
さて、前述のお悩み解決に対処する方法です。
ポイントはズバリ二つ。
以下の内容で構成していきます。
今回はDockerでRedocとして表示させるので、Redocの公式イメージを使います。
/
├── compose.yaml # Docker Compose
├── docker
│ └── redoc
│ ├── Dockerfile
│ ├── bundle.ts # バンドラー
│ └── start.sh # Docker EntryPoint
└── api_docs/ # 分割されまくったOpenAPIたち
├── components/
│ ├── index.yaml
│ ├── parameters/
│ │ ├── index.yaml
│ │ ├── path/
│ │ └── query/
│ ├── requestBodies/
│ │ ├── index.yaml
│ │ └── xxx/
│ ├── responses/
│ │ ├── index.yaml
│ │ └── xxx/
│ ├── schemas/
│ │ ├── xxx.yaml
│ │ └── index.yaml
│ └── securitySchemes/
│ ├── xxx.yaml
│ └── index.yaml
├── paths/
│ ├── index.yaml
│ └── xxx/
│── tags
│ └── xxx.yaml
├── index.yaml
└── openapi.yaml # バンドルされたOpenAPI
version: "3.8"
services:
redoc:
build:
context: .
dockerfile: ./docker/redoc/Dockerfile
volumes:
- ./docker/redoc/bundle.ts:/app/bundle.ts
- ./docker/redoc/start.sh:/app/start.sh
- ./api_docs:/usr/share/nginx/html/docs # Redoc公式イメージでは '/usr/share/nginx/html/' 配下にOpenAPIを置く必要がある
ports:
- 9000:80
# Redoc公式イメージをベースに
FROM redocly/redoc:v2.0.0
WORKDIR /app
# ホットリロードにnpmパッケージを使用するためnpmインストール
RUN apk add --no-cache npm
# npmパッケージをコンテナ内でしようするための設定
ARG NODE_MODULE_PATH=/node
RUN npm config set prefix=${NODE_MODULE_PATH}
ENV PATH $PATH:${NODE_MODULE_PATH}/bin
ENV NODE_PATH ${NODE_MODULE_PATH}/lib/node_modules
# レンダリングするOpenAPIのPath (Redoc公式イメージの環境変数)
# ファイル分割された '$ref' が含まれていると上手く参照できずエラーになる
# そのため、バンドル前の 'index.yaml' は使わず、バンドルされたファイルを指定する
ENV SPEC_URL docs/openapi.yaml
# ホットリロードに使用するnpmパッケージ群をインストール
RUN npm i -g \
forever \
ts-node \
chokidar \
swagger-merger
# EntoryPointにShellスクリプト指定
ENTRYPOINT [ "sh", "start.sh" ]
# RedocのNginxを立ち上げる
# 'docker/redoc/Dockerfile'の 'ENTRYPOINT' で公式イメージの ' ENTRYPOINT ' は上書きされてしまったので再定義
# '&' を付けてバックグラウンドプロセスにして、後述のバンドラーのプロセスと並走できるようにする
source /usr/local/bin/docker-run.sh &
# バンドラー実行
ts-node bundle.ts
// 'ts-ignore' 連発。非常に良くないので反面教師で。。。
// @ts-ignore
const chokidar = require("chokidar") // ウォッチャー
// @ts-ignore
const swaggerMerger = require("swagger-merger") // バンドラー
const rootPath = "/usr/share/nginx/html/docs/" // OpenAPIが置かれているPath
const rootFile = `${rootPath}index.yml` // OpenAPIのルートファイルPath
const mergedFile = `${rootPath}openapi.yml` // バンドルしたOpenAPIを吐き出すPath
chokidar
.watch(rootPath, { ignored: mergedFile })
.on("all", (event: string, path: string) => {
// ウォッチャーの検知したイベントをログに出力
console.log(event, path)
// OpenAPIをバンドル
swaggerMerger({ input: rootFile, output: mergedFile })
.then(() => {
console.log("ReBundled OpenAPI")
})
.catch((err: object) => {
console.error(err)
})
})
今回はOpenAPIにまつわる開発体験の向上を軸にご紹介しました。
割と良くある構成なのかな?とも思いますし、ツールの選定によってはもっとシンプルにすることができるかと思います。
もっと良い構成をご紹介できるように、チャンスがあれば色々試していきたいと思います。
最後まで読んでくださりありがとうございました!
では!ノシ
1,203
タム
2023.07.07
426
新井公貴
2023.06.27