仕様変更で衰退する人類
↓スーパーシステムエンジニアのとある1日の流れ↓
- DB定義が変更になった
- DDLなりDB定義書を直す
- DBに適用
- メンバーにExcelのAPI仕様書変更をしてもらう ←ここで衰退する
- 別な変更が足りてなかったらしいので自分で追記 ←ここで衰退する
- Excelなのでgitで差分がよくわからず全部見直す ←ここで衰退する
- ソースコードを直す ←ここで衰退する
- レビューで粗が見つかった場合は4に戻る ←ここで衰退する
- 動作確認する ←ここで衰退する
- 本番デプロイする ←ここで衰退する
- お客様(神様)に一報いれる ←ここで衰退する
- お客様(神様)からフィードバックがきて更にDB定義が変わる、1に戻る ←ここで衰退する
OpenAPI (Swagger)便利すぎウケた
Excelアレルギーの人のための課題の解決案として、最近Qiitaでも目にするOpenAPI(Swagger)を導入してみる。
・公式
https://swagger.io/resources/open-api/
要するにYAMLファイルを書くだけで
- いい感じのAPI仕様書(絶妙なデザイン)
- いい感じのサーバ側テンプレート(いろんな言語で)
- いい感じのクライアント側テンプレート(いろんな言語で)
を出力できる所謂おもしろツール。
オンライン上でYAMLを書いてその場でレンダリングするSwagger Editorや、サードパーティのコンバーターなどもそこそこある。
V2までSwaggerと呼ばれつつ、V3から公にOpenAPIと呼ぶようになったらしいので、困ったときにググるのがしんどく、それぞれの派閥の抗争が絶えなくて衰退する。
ググり力はさておき、YAMLのメンテだけでAPIの仕様書作成~実装が回るなら衰退しなさそうなので使ってみる。
とりあえず書く
こんな感じでYAMLを書く↓
openapi: 3.0.1
info:
title: API定義書
description: |
各APIのインターフェース定義書。
version: 1.0.0
paths:
/login:
post:
tags:
- login
summary: API001_LOGIN
description: ユーザID、パスワードを送信し認証処理を行う。
operationId: API001
parameters:
- in: header
name: Content-Type
description: リクエストボディの型を指定。application/json を指定する。
schema:
type: string
required: true
- in: header
name: Api-Token
description: 認証用APIトークン。
schema:
type: string
required: true
requestBody:
content:
application/json:
schema:
type: object
title: LoginRequest
required:
- user_id
- password
properties:
user_id:
description: ID
type: string
maxLength: 100
minLength: 0
password:
description: パスワード
type: string
maxLength: 25
minLength: 25
responses:
'200':
description: 成功時のレスポンス
content:
application/json:
schema:
type: object
title: LoginResponse
properties:
result:
type: boolean
example: true
data:
type: object
properties:
user_token:
description: 認証に成功し、発行されたユーザトークン
type: string
example: thismightbetoken
'500':
description: 失敗時のレスポンス
content:
application/json:
schema:
type: object
properties:
result:
type: boolean
error:
type: object
properties:
message:
description: エラーメッセージ
type: string
code:
description: エラーコード
type: string
example:
result: false
error:
message: エラーが発生しました。
code: ERR01
↑を↓にコピペするといい感じにレンダリングしてくれる。
・Swagger Editor
https://editor.swagger.io/
記法に間違いがあればエラー吐いてくれるし、上部メニューの「Generate Server」「Generate Client」で任意の言語で仕様に沿ったテンプレを吐いてくれる便利っぷり。
長すぎて衰退しそう
本題。
ymlファイルがテキスト形式なのでgitで差分も見れるし Excelの5億倍くらいは 開発しやすくなって本当に良かった。
ほんで調子に乗ってpathsの下にどんどんAPIの仕様を追加していくものの、以下で衰退しそうになる。
- APIが1つ増えるごとに数百行増える
- 使い回せるRequest/Responseはcomponent化して $ref参照で使い回せる けど、結局それも下の方に書くのでどんどん長くなっていく
- API毎にいい感じのファイル管理したい
swagger-mergerという概念
・swagger-merger
https://www.npmjs.com/package/swagger-merger
ぽい名前のツールがあったのでnpmで突っ込む。
いい感じにファイルを分割するという概念
swagger-merger様がindex.ymlから$refで各ファイルを辿ってマージしてくれるので、API仕様書.ymlを分割して各ディレクトリ、各ファイルに分ける。
・分割したファイルとフォルダ構成
.
│ API仕様書.yml(swagger-mergerで自動生成)
│
└─src
│ index.yml
│
├─components
│ │
│ ├─request
│ │ LoginRequest.yml
│ │ LogoutRequest.yml
│ │
│ └─response
│ LoginResponse.yml
│ CommonSuccessResponse.yml
│ CommonErrorResponse.yml
│
└─paths
API001_ログイン.yml
API002_ログアウト.yml
とりあえずこんな感じにしてみる。
openapi: 3.0.1
info:
title: API定義書
description: |
各APIのインターフェース定義書。
version: 1.0.0
paths:
/login:
$ref: './paths/API001_ログイン.yml'
/logout:
$ref: './paths/API002_ログアウト.yml'
components:
parameters:
apiTokenHeader:
in: header
name: Api-Token
description: 認証用APIトークン。
schema:
type: string
required: true
contentTypeHeader:
in: header
name: Content-Type
description: リクエストボディの型を指定。application/json を指定する。
schema:
type: string
required: true
↑このファイルを根っこに各ファイルを辿ってくれる。
post:
tags:
- login
summary: A01_ログイン
description: ユーザID、パスワードを送信し認証処理を行う。成功時はユーザー固有のトークンを返却する。
operationId: a01login
parameters:
- $ref: '../index.yml#/components/parameters/contentTypeHeader'
- $ref: '../index.yml#/components/parameters/apiTokenHeader'
requestBody:
content:
application/json:
schema:
$ref: '../components/request/LoginRequest.yml'
responses:
200:
description: 成功時のレスポンス
content:
application/json:
schema:
$ref: '../components/response/LoginResponse.yml'
500:
$ref: '../components/response/CommonErrorResponse.yml'
↑path配下はAPI毎の機能概要とか書く。
実際のリクエスト/レスポンスのパラメータはそれぞれ別ファイル管理にした。
ちなみにヘッダーはindex.yml下部のcomponentで共通化してしまっている。
type: object
title: LoginRequest
required: # 必須フィールド
- user_id
- password
properties:
user_id:
description: ID
type: string
maxLength: 100
minLength: 0
password:
description: パスワード
type: string
maxLength: 25
minLength: 25
↑components/request配下は各APIのリクエスト仕様を記載したymlを配置する。
type: object
title: LoginResponse
properties:
result:
type: boolean
example: true
data:
type: object
properties:
user_token:
description: 認証に成功し、発行されたユーザトークン
type: string
example: thismightbetoken
↑同様にレスポンス。登録系でtrue/falseしか返さないレスポンスや、共通のエラーレスポンスなんかは1ファイル作って使い回す。
満を持してマージ
(npm版)
swagger-merger -i ./src/index.yml -o ./API仕様書.yml
「src/index.yml」を基にして各ファイルを辿って「API仕様書.yml」を作成してくれる
あとは出力したAPI仕様書.ymlからOpenAPI必殺のジェネレート機能を使って自動生成祭りにする。
前述したSwagger Editorにコピペして「Generate Server」なり「Generate Client」なりして遊んでれば衰退しなさそう。
この辺をコマンドでやりたい場合はopenapi-codegenだのswagger-codegenだの公式で色々あって、だいたい同じようなことがローカルのコマンドでできる。
https://openapi-generator.tech/docs/installation
今はdockerで↓みたいなことをして、ymlのマージ直後にその生成物からHTMLの仕様書を出力してる。
docker run --rm -v %cd%:/local swaggerapi/swagger-codegen-cli-v3:3.0.9 generate -i /local/API仕様書.yml -l html2 -o /local/html
あと見直してて思ったけどindex.ymlの下にあるcomponents.parameters以下でファイル管理すると衰退しにくいかも。
~fin~
このあと、共通で使い回せそうだけど微妙に仕様が異なるcomponentとかリクエスト/レスポンスがたくさん出てきてしまい、膨大なファイル管理に追われて人類は衰退する。