こちらは前回の記事 ぼくのかんがえたさいきょうのOpenAPI(Swagger) 仕様の策定環境 その2 docker-composeでのSwagger UI の導入、前々回の記事 ぼくのかんがえたさいきょうのOpenAPI(Swagger) 仕様の策定環境 その1 redocly(yamlのLINT)の導入 の続きとなります。
記事の背景は前々回の記事をご参照ください
当記事では、 OpenAPIの定義されたyamlファイルを docker-composeを使ってローカルでPrismを使ってモックサーバーを構築すること、策定したPrismのモックサーバーを Swagger UIからコールできるようにすることを目的としています。
この記事でわかること
-
OpenAPIのyamlファイルのlint方法前々回の記事をご参考ください -
OpenAPIのyamlファイルへ独自lint(カスタムルール)の追加方法前々回の記事をご参考ください -
docker-composeを使って swagger uiの構築方法と複数ファイルAPI定義があった場合のdocker-composeの実装方法前回の記事をご参考ください - docker-composeを使って OpenAPIの定義からmockserverを起動する方法と ホットリロードの実装方法
皆さんはじめまして株式会社クライドでオフショア開発部門の事業部長とフィリピン支社のCEOをしている Matsuo です。
今回、私が取り組んでいるプロジェクトにて、FrontendはNextJS , BackendはJavaで多くのAPIを開発する必要があったため、開発をよりスムーズにし、Frontendはバックエンドの実装を待たずにAPIをコールできるようにするのと、FrontendとBackendでAPIの形式の認識齟齬と実装上の違いが起きないようにするために、OpenAPIの定義を使ってFrontendとBackendで共通のAPI仕様を使って開発できるようにする実装方針を選定しました。
そこで、Swagger UIや LINT、Mockserverなどいくつか構築に手間取った箇所があったため、共有します。
前提
複数ファイルのOpenAPIの定義ファイルを持っており、複数の定義に合わせて動作可能なモックサーバーを構築します。
モックサーバーが必要な背景としては、プロジェクトのAPI数が多く、フロントエンドのエンジニアがバックエンドの実装完了を待たずに実装できるようにするためです。
また、フロントエンドエンジニアはOpenapiの定義を担保したモックサーバーをコールすることによって、バックエンドで仕様と実装の違いが生まれてしまう可能性を考慮せずに、教科書のみを信じて実装を進めることができます。
モックサーバーの候補はいくつかありましたが、今回は Prism を使ってAPIサーバーを構築しています。
プロジェクトのファイル環境
私のプロジェクトでは以下ファイル構成で環境を構築しています
.
├── infra_structure
│ ├── Caddyfile
│ ├── CustomPrism.Dockerfile
│ ├── docker-compose.yaml
│ └── start-prism.sh
├── node_modules
├── openapi
│ ├── hoge-api.yaml
│ ├── common
│ │ ├── error-response.yaml
│ │ ├── parameters.yaml
│ │ ├── request-bodies.yaml
│ │ └── schema.yaml
│ ├── fuga-api.yaml
├── package-lock.json
├── package.json
├── plugins
│ ├── custom-lint-rules.js
│ └── rules
│ ├── enum-snake-case.js
│ ├── operation-id-camel-case.js
│ ├── query-param-snake-case.js
│ ├── schema-property-snake-case.js
│ ├── tags-kebab-case.js
│ └── tags-kebab-case.js.gz
├── readme.md
└── redocly.yaml
openapi/hoge-api.yamlと、openapi/fuga-api.yamlがメインのOpenAPIの定義ファイルとなり、それぞれがcommon配下のyamlを読み込む構成となっています。
dockerの構成に関するファイルは、 infra_structure配下へ設置しています。
infra_structure/docker-compose.yaml の定義内容
version: "3"
services:
proxy:
image: caddy
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
ports:
- "8180:80"
depends_on:
- hoge_api
- fuga_api
- swagger_ui
swagger_ui:
image: swaggerapi/swagger-ui
volumes:
- ../openapi:/usr/share/nginx/html/api
ports:
- "8181:8080"
environment:
URLS:
>
[
{
url: "api/hoge-api.yaml",
name: "hoge-api",
},
{
url: "api/fuga-api.yaml",
name: "fuga-api",
},
]
PORT: 8080
hoge_api:
build:
context: .
dockerfile: CustomPrism.Dockerfile
volumes:
- ../openapi:/openapi
command: ["mock -d -p 4010 --host 0.0.0.0 /openapi/hoge-api.yaml"]
fuga_api:
build:
context: .
dockerfile: CustomPrism.Dockerfile
volumes:
- ../openapi:/openapi
command: ["mock -d -p 4010 --host 0.0.0.0 /openapi/fuga-api.yaml"]
※当yamlに出てくるswagger_ui については前回の記事を参照してください
起動コマンド
cd infra_structure
docker-compose up
モックサーバーへのリクエスト
ポート番号:8180を以下箇所にて割り当てているため、URLは http://localhost:8180/
がベースとなります。
caddyを使い、内部のDockerファイルへproxyしている構成となります。
ports:
- "8180:80"
設定のポイント
Cadyfileの設定
Caddyについては公式をご参考ください、dockerでよく使うnginx-proxyと類似した挙動をするサービスとなります。
https://caddyserver.com/
当プロジェクトでの設定しているCadyfileは以下のようにしています
http://localhost
route /hoge-api/* {
uri strip_prefix /hoge-api
reverse_proxy hoge_api:4010
}
route /fuga-api/* {
uri strip_prefix /fuga-admin
reverse_proxy fuga_api:4010
}
このように localhostのURLをベースに /hoge-api/ へのアクセスは、 hoge_apiコンテナの4010ポートへ、
/fuga-api/ へのアクセスは fuga_apiコンテナの4010ポートへproxyするようにしています。
尚、 uri strip_prefix /fuga-admin
の記述によって、URLに入力されている /fuga-admin
を取り除いて、実際のAPIが動作するコンテナへproxyするようになっています。
各OpenAPIのyaml毎にDockerコンテナを起動するようにする
hoge_api:
build:
context: .
dockerfile: CustomPrism.Dockerfile
volumes:
- ../openapi:/openapi
command: ["mock -d -p 4010 --host 0.0.0.0 /openapi/hoge-api.yaml"]
fuga_api:
build:
context: .
dockerfile: CustomPrism.Dockerfile
volumes:
- ../openapi:/openapi
command: ["mock -d -p 4010 --host 0.0.0.0 /openapi/fuga-api.yaml"]
こちらの処理で 各APIのyaml毎に Dockerコンテナを起動するように設定しています。
当構成では、Prismを採用しており、Prismは元々のDockerfile がありますが、
Prismの Dynamic Response Generation with Faker 機能を使って 動的にmockserverのレスポンスを制御したかったため、モックサーバーで動作を確認しながら実装できるようにするために、
yamlの定義を更新したらホットリロードをできる Dockerイメージを作成するようにしました。
ぜひ本家の Dockerimageで今後対応してほしいところです。
ホットリロード対応の Docker作成
ホットリロード対応のDockerは以下ファイル構成で作成しています
.
├── infra_structure
│ ├── CustomPrism.Dockerfile
│ └── start-prism.sh
各ファイルの中身は以下のようになっています
################
# PrismのDockerfileは、hotload機能がないため、Hotloadを実装する
# Dockerfile単体での実行サンプル(事前に custom-prismとしてbuildしている必要があります)
# docker run --rm -it -p 4010:4010 -v $PWD:/tmp custom-prism "mock -d -p 4010 /tmp/openapi/hoge-api.yaml"
################
# Dockerfile
FROM stoplight/prism:4
# nodemonのインストール
RUN npm install -g nodemon
RUN chmod +x /usr/src/prism/packages/cli/dist/index.js
# start-prism.sh スクリプトをコピー
COPY ./start-prism.sh /start-prism.sh
RUN chmod +x /start-prism.sh
# デフォルトコマンドの設定
ENTRYPOINT ["nodemon", "--watch", "/openapi/", "--ext", "yaml,yml", "--exec", "sh /start-prism.sh"]
nodaemonのファイル監視の対象ディレクトリを /tmp/ /openapi/ に固定されてしまっているのはあまり関心できる実装ではありませんが、ここでは目をつぶっています。 (2023/12/21 追記, /tmpへ固定すると、prismが生成した一時ファイルがローカルへ反映してしまうためパスを変更)
内部で使用している start-prism.shは以下のようにしています。
#!/bin/sh
# start-prism.sh
# 全ての引数を一つの文字列として受け取る
PRISM_COMMAND="prism $@"
# コマンドを評価して実行
eval $PRISM_COMMAND
Dockerファイルでは、 prismのImageをベースに、nodemonをインストールし、/tmp/配下のyaml,ymlファイルの変更監視しています。
変更されると、 sh /start-prism.sh
にて prismが再起動されるようになっています。
start-prism.sh では コマンドライン引数にパラメータが文字列として渡され、それを 文字列で prismコマンドと結合し、evalすることでPrismの実行をしています。
この流れを行うことにより、本来の Dockerfileである stoplight/prism:4
とほぼコールの仕方を変更せずに、ホットリロードに対応することができるようになりました。
docker-compose.yamlでは stoplight/prism:4
のイメージを使うかわりに、上述の独自のDockerfileを使用するようにしています。
docker-composeのcommandの箇所で、最終的にprismに渡したい引数を定義しています。
引数を外部から受け渡して実行できるようにすることで、Prismのモックサーバー以外の機能や、モックサーバーへ渡したいパラメーターをDockerfileを更新せずに変更できるようにしています。
command: ["mock -d -p 4010 --host 0.0.0.0 /tmp/fuga-api.yaml"]
モックデータではなくsampleデータで返却させたい場合は、 -d
のオプションを削除することで動作を変更できます。
Prismでモックデータを返却出力し出力内容を制御できるようにする
モックデータの出力は、Prismの起動コマンドにて -d
オプションをつけることで Dynamic Response Generation with Faker の機能が有効になります。
具体的には、x-fakerパラメーターをOpenapiの各yamlの定義(例えばSchema)の各フィールドへ追加することで、生成されるモックデータをコントロールすることが出来ます。
これは、faker-js の機能を執筆時点では使用しています。
使用方法は以下のように x-faker
のパラメーターを設定します
Pet:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
x-faker: name.firstName
example: doggie
photoUrls:
type: array
items:
type: string
x-faker: image.imageUrl
これらの name.firstName
や image.imageUrl
は、 fakerオブジェクトのmethod名を指しています。
尚、当記事執筆時点(2023/12/20)では、 stoplight/prism:4
は faker-jsの version:6 に依存しているため、
最新のfaker-js(執筆時点ではversion:8) のドキュメントは参考になりませんでした。
Version6のドキュメントを参照する必要があります(オブジェクトグループ名がversion8と異なります)
x-fakerのパラメーター定義
yaml上に記載する x-fakerパラメーターは、faker-jsのmethod名の指定以外に、methodへ引き渡したいパラメーターを設定することができます。
配列でのパラメーター定義
例えばfirstNameを生成したい場合、
Person:
type: object
properties:
first_name:
type: string
x-faker: name.firstName
example: yuki
とする以外に、
Person:
type: object
properties:
first_name:
type: string
x-faker:
name.firstName:
- male
example: yuki
として配列でパラメーターを設定することで faker.name.firstName("male")
と同じ実行結果を得ることができます。
name.firstNameについての仕様はこちらです。
オブジェクトでのパラメーター定義
datatype.number のように引数がオブジェクトとなっている場合は、以下のようにすることで引数を設定することができます。
Person:
type: object
properties:
age:
type: integer
x-faker:
datatype.number:
min: 100
max: 250
example: 40
faker-jsの日本語化 / Prismの挙動変更
faker-jsでは日本語化機能を持っています。
Prism経由でfaker-jsを使用して言語を切り替える場合は、 yamlへ定義を追加します。
また、Prismがarrayオブジェクトなどで自動で生成するデータ個数もyaml上で定義することができます。
設定できるオプションはこちらで確認することができます。
x-json-schema-faker:
locale: ja
min-items: 3
max-items: 10
resolve-json-path: true
日本語化した場合にemailアドレスの@マークより前が日本語になってしまう
日本語化を実装して、emailアドレスを fakerの internet.emailを使ったところ、@より前が日本語で出力されてしまいました。
例:山田花子_53@gmail.com
これでは、frontへ組み込んだときに意図しないバリデーションエラーが発生してしまうおそれがあります。
そのため、以下のようにfistNameとlastNameのパラメーターを付与することでこの課題を解決することができました。
email:
type: string
format: mailaddress
x-faker:
internet.email:
- "test"
- "last-name"
example: example@gmail.com
同じくDockerで立てたSwagger UIからMockserverをUI上でコールできるようにする
Swagger UIでは、yamlのserversプロパティを設定することで、Swagger UIからリクエストできるサーバーを選択することができます。
servers:
- url: '{ApiBaseURL}/hoge-api'
variables:
ApiBaseURL:
enum:
- https://api.admin.example.com
- "http://localhost:8180"
default: "http://localhost:8180"
description: Your server host
こちらを設定することで、以下画像のようにリクエスト先サーバーを選択できます。
この設定をすることで、Swagger UIの 各APIの Try it out ボタンより、モックサーバーへ実際にリクエストを送信できるようになりました。
以上がdocker-composeで mockserverを立て、実際のPrismのパラメーターを制御しモックサーバーとして使用可能な状態にした内容となります。
OpenAPIで定義を作成して実装を進めていくようなスキーマ駆動開発を進めようとされている方や、スキーマ駆動開発に向けたスキーマ定義環境の環境構築をされているような方に当3記事が参考になれば幸いです。
- ぼくのかんがえたさいきょうのOpenAPI(Swagger) 仕様の策定環境 その1 redocly(yamlのLINT)の導入
- ぼくのかんがえたさいきょうのOpenAPI(Swagger) 仕様の策定環境 その2 docker-composeでのSwagger UI の導入
- ぼくのかんがえたさいきょうのOpenAPI(Swagger) 仕様の策定環境 その3 docker-composeでのmockserver(Prism) の導入(当記事)
次回、openApiのyamlからNextJSで利用できるAPI Clientをtypescriptで自動生成できるようにした を執筆予定です。