OpenAPI(Swagger)を使ってRestAPIの仕様を策定し、Lint , Mockserver , Swagger UI機能を使えるようにしたので、解説します。
当記事では、まず redoclyを導入し、lintを実行するまでの記事としています。
続きは別記事にて紹介できればと思います。
記事の概要
この記事でわかること
- OpenAPIのyamlファイルのlint方法
- OpenAPIのyamlファイルへ独自lint(カスタムルール)の追加方法
-
docker-composeを使って swagger uiの構築方法と複数ファイルAPI定義があった場合のdocker-composeの実装方法別記事にします -
docker-composeを使って OpenAPIの定義からmockserverを起動する方法と ホットリロードの実装方法別記事にします
この記事で触れないこと
- OpenAPIのyamlの書き方
皆さんはじめまして株式会社クライドでオフショア開発部門の事業部長とフィリピン支社のCEOをしている Matsuo です。
今回、私が取り組んでいるプロジェクトにて、FrontendはNextJS , BackendはJavaで多くのAPIを開発する必要があったため、開発をよりスムーズにし、Frontendはバックエンドの実装を待たずにAPIをコールできるようにするのと、FrontendとBackendでAPIの形式の認識齟齬と実装上の違いが起きないようにするために、OpenAPIの定義を使ってFrontendとBackendで共通のAPI仕様を使って開発できるようにする実装方針を選定しました。
そこで、Swagger UIや LINT、Mockserverなどいくつか構築に手間取った箇所があったため、共有します。
OpenAPI定義のLINT
各OpenAPI定義のyamlのLINTには、Redocly CLI を選定しました。
redoclyでは yaml定義のLINTの他に、APIドキュメントの生成などの機能を持っています。
今回は、ドキュメント機能は Swagger UIを使うため、 LINT機能のみにフォーカスして紹介します。
早速ですが、LINTの実行は npm での実行と Dockerでの実行が行なえますが、今回はJavaの開発メンバーもいるため、簡単に導入できるよう Dockerでの実行と、CI用でもチェックを行うため、npmを使った実行の両方を行っています。
ルールの定義
Redocly CLIでのLINTチェックのルールは、 /redocly.yamlにて定義することができます。
私は redocly.yaml へは以下のように設定しています
apis:
hogeApiGroup@v1:
root: openapi/hoge-api.yaml
fugaApiGroup@v1:
root: openapi/fuga-api.yaml
extends:
- recommended
plugins:
- './plugins/custom-lint-rules.js'
rules:
info-license: off
no-unused-components: error
my-local-plugin/query-param-snake-case: error
my-local-plugin/schema-property-snake-case: error
my-local-plugin/operation-id-camel-case: error
my-local-plugin/tags-kebab-case: error
my-local-plugin/enum-snake-case: error
theme:
openapi:
htmlTemplate: ./docs/index.html
generateCodeSamples:
languages: # Array of language config objects; indicates in which languages to generate code samples.
- lang: curl
- lang: Node.js
- lang: JavaScript
APIの定義を openapi/配下に格納し、 ルールの拡張を ./plugins/配下に定義しています。
ここではクエリパラメーターや、スキーマのプロパティの命名規則、タグの命名規則、operationIdの命名規則、enum値の命名規則を定義しています。
デフォルトの機能でもできそうだったのですが、実装方法を見つけるまで至れなかったため、独自で拡張しています。
プラグインのルールの読み込みは
plugins:
- './plugins/custom-lint-rules.js'
この箇所で読み込んでいます。
plugins/custom-lint-rules.js では以下のようにカスタムルールの読み込みを実装しています
const QueryParamSnakeCase = require('./rules/query-param-snake-case')
const SchemaPropertySnakeCase = require('./rules/schema-property-snake-case')
const OperationIdCamelCase = require('./rules/operation-id-camel-case')
const TagsKebabCase = require('./rules/tags-kebab-case')
const EnumSnakeCase = require('./rules/enum-snake-case')
module.exports = {
id: 'my-local-plugin',
rules: {
oas3: {
'query-param-snake-case': QueryParamSnakeCase,
'schema-property-snake-case': SchemaPropertySnakeCase,
'operation-id-camel-case' : OperationIdCamelCase,
'tags-kebab-case' : TagsKebabCase,
'enum-snake-case': EnumSnakeCase
},
},
}
例えば、plugins/rules/query-param-snake-case.js では以下のような実装になっています
module.exports = QueryParamSnakeCase;
function QueryParamSnakeCase() {
return {
Parameter: {
enter(parameter, ctx) {
if (parameter.in === 'query') {
// $refの解決
if ('$ref' in parameter) {
const resolved = ctx.resolve(parameter.$ref);
if (resolved.result) {
parameter = resolved.result;
}
}
// スネークケースの正規表現
const snakeCaseRegex = /^[a-z0-9]+(?:_[a-z0-9]+)*$/;
// パラメータ名のチェック
if (!snakeCaseRegex.test(parameter.name)) {
ctx.report({
message: `The query parameter "${parameter.name}" must be a snake case.`,
location: ctx.location.child('name'),
});
}
}
},
},
};
}
当プロジェクトでは、 openapiの定義yamlから外部yamlファイルにて定義した schemaや queryParameterのyamlをロードしており、ロードしている外部ファイルであってもLintのルールを適応するため、 ctx.resolve
にて $ref を解決する処理をいれています。
ですが、階層が深いとうまく読み込めないケースもあり、バージョンアップに期待したいところです。
カスタムルールの定義は、公式のページを参考に実装することができます。
また、カスタムルール設定時に、どのNodeTypeに対して実行するかによって $refの値を読み込めたり、読み込めなかったりということが発生しました。
その際のこちらの node type tree のページが参考になりましたので紹介いたします。
尚、以下のようなopenapiの定義としております。
paths:
/login:
post:
security: []
tags:
- auth
operationId: login
summary: login
description: login
requestBody:
$ref: './common/request-bodies.yaml#/LoginRequest'
responses:
'200':
description: succeeded
content:
application/json:
schema:
$ref: './common/schema.yaml#/AuthenticationToken'
'400':
$ref: './common/error-response.yaml#/BadRequest'
'401':
$ref: './common/error-response.yaml#/Unauthorized'
Lint(readocly lint)の実行
実行は npm run ~~~ と、 Dockerでの実行、GithubでのCIの実行をできるようにしています。
NPMでの実行
インストール
npm install @redocly/cli
{
"scripts": {
"lint": "redocly lint"
},
"dependencies": {
"@redocly/cli": "^1.4.1"
}
}
実行
npm run lint
Dockerでの実行
docker run --rm -v $PWD:/spec redocly/openapi-cli lint
/spec配下にcurrent directoryをマウントすることによって、redocly.yamlの定義をそのまま使えるようにしています。
GithubのCIでの実行
name: Test
on:
pull_request:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
test-and-build:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 20
cache: 'npm'
cache-dependency-path: ./package-lock.json
- name: Install dependencies
run: npm ci
- name: Run LINT
run: npm run lint
github actionsでは npmにて定義したlintコマンドを実行するようにしています。
以上で、OpenAPIのyamlを書いた後に、変更したyamlがルール通りに書くことが出来ているかを
ローカルとCIでチェックすることで、所定のフォーマット通りに設計されていることを担保することがでるようになりました。
これで人によって命名規則の違いや、パラメーターの不足、入力誤りなどを防ぐことができ、安心して変更・追加していくことができます。
その他
VScodeをお使いの場合は、Redocly OpenAPI を入れておくことで、openapiのyaml定義時にsuggestしてくれたりしますのでおすすめです。
次回は、docker-composeを使ってSwagger UI にて、定義したopenapiのyamlをローカルで表示するまでを記事にしたいと思います。