脱Excel仕様書しつつOpenAPI(Swagger)のYAMLを分割して管理する

# 仕様変更で衰退する人類:hugging:

↓:blush:スーパーシステムエンジニアのとある1日の流れ:blush:↓

1. DB定義が変更になった
1. DDLなりDB定義書を直す
1. DBに適用
1. メンバーにExcelのAPI仕様書変更をしてもらう **←ここで衰退する**
1. 別な変更が足りてなかったらしいので自分で追記 **←ここで衰退する**
1. Excelなのでgitで差分がよくわからず全部見直す **←ここで衰退する**
1. ソースコードを直す **←ここで衰退する**
1. レビューで粗が見つかった場合は4に戻る **←ここで衰退する**
1. 動作確認する **←ここで衰退する**
1. 本番デプロイする **←ここで衰退する**
1. お客様(神様)に一報いれる **←ここで衰退する**
1. お客様(神様)からフィードバックがきて更にDB定義が変わる、1に戻る **←ここで衰退する**


# OpenAPI (Swagger)便利すぎウケた:hugging:

~~Excelアレルギーの人のための~~課題の解決案として、最近Qiitaでも目にするOpenAPI(Swagger)を導入してみる。

・公式
https://swagger.io/resources/open-api/

要するにYAMLファイルを書くだけで

+ いい感じのAPI仕様書(絶妙なデザイン)
+ いい感じのサーバ側テンプレート(いろんな言語で)
+ いい感じのクライアント側テンプレート(いろんな言語で)

を出力できる所謂**おもしろツール。**

オンライン上でYAMLを書いてその場でレンダリングする[Swagger Editor](https://editor.swagger.io/)や、サードパーティのコンバーターなどもそこそこある。
V2までSwaggerと呼ばれつつ、V3から公にOpenAPIと呼ぶようになったらしいので、困ったときにググるのがしんどく、それぞれの派閥の抗争が絶えなくて衰退する。

ググり力はさておき、YAMLのメンテだけでAPIの仕様書作成～実装が回るなら衰退しなさそうなので使ってみる。

# とりあえず書く:hugging:

こんな感じでYAMLを書く↓

```API仕様書.yml

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」で任意の言語で仕様に沿ったテンプレを吐いてくれる便利っぷり。

# 長すぎて衰退しそう:hugging:

**本題。**

ymlファイルがテキスト形式なのでgitで差分も見れるし ~~Excelの5億倍くらいは~~ 開発しやすくなって本当に良かった。
ほんで調子に乗ってpathsの下にどんどんAPIの仕様を追加していくものの、**以下で衰退しそうになる。**

+ APIが1つ増えるごとに数百行増える
+ 使い回せるRequest/Responseはcomponent化して[ $ref参照で使い回せる ](https://swagger.io/docs/specification/using-ref/)けど、結局それも下の方に書くのでどんどん長くなっていく
+ API毎にいい感じのファイル管理したい

# swagger-mergerという概念:hugging:

・swagger-merger
https://www.npmjs.com/package/swagger-merger

ぽい名前のツールがあったのでnpmで突っ込む。

[dockerでやりたいならこっち](https://hub.docker.com/r/properdom/swagger-merger/)


# いい感じにファイルを分割するという概念:hugging:

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
```

とりあえずこんな感じにしてみる。

---

```src/index.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
```

↑このファイルを根っこに各ファイルを辿ってくれる。

---

```path/API001_ログイン.yml
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で共通化してしまっている。

---

```components/request/LoginRequest.yml
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を配置する。

---

```components/response/LoginResponse.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ファイル作って使い回す。

---

# 満を持してマージ:hugging:


(npm版)

```
swagger-merger -i ./src/index.yml -o ./API仕様書.yml
```
**「src/index.yml」を基にして各ファイルを辿って「API仕様書.yml」を作成してくれる:hugging:**
**あとは出力したAPI仕様書.ymlからOpenAPI必殺のジェネレート機能を使って自動生成祭りにする。**

前述した[Swagger Editor](https://editor.swagger.io/)にコピペして「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～:hugging:
このあと、共通で使い回せそうだけど微妙に仕様が異なるcomponentとかリクエスト/レスポンスがたくさん出てきてしまい、膨大なファイル管理に追われて人類は衰退する。

---

### [:thinking:書いた人のTwitter:thinking:](https://twitter.com/suguruguruguruO)