API仕様書に求めるものについての考察

この記事は [ただの集団 Advent Calendar 2020](https://adventar.org/calendars/5581) の25日目の記事です。

## 目次
- 概要
- 背景
- 今回想定するユースケース
- API仕様書での記述
- テスティングツール
- まとめと所感
- 参考資料

## 概要
サーバーサイドエンジニアとしてWeb APIの開発を行うにあたって、API仕様書をもとに開発を進めています。
仕様書と実装コードを書きながらふと、以下のようなことを考えていました:thinking:

1. リクエストによって**レスポンスが微妙に違うパターン**を表現したい
2. 実装したAPIアプリケーションが**仕様通りに動作しているか**を検証したい (具体的にはレスポンスの**JSON Schema Validation**をしたい)

2.については「OpenAPIからソースコードを自動生成して開発する」というルールに則っていれば、理論上両者のschemaがずれることは無いのですが、実挙動を手軽に確認・保証する手段があれば安心感が増すよなぁと思った次第です。



## 背景
昔の勉強会で [Apiドキュメンテーションツールを使いこなす【api blueprint編】](https://www.slideshare.net/dcubeio/apiapi-blueprint) を発表したのですが、当時はまだSwagger2だったこともあり、API Blueprintをメインで使っていました。
現在はOpenAPI3がデファクトになりつつあると感じており、実開発での仕様書もOpenAPIで記述しています。

そういった環境の変化の過程で取り巻くツール群もガラリと変わったため、キャッチアップも兼ねて調べてみることにしました。

(余談)
今でも個人的にはAPI Blueprintを評価しているのですが、OpenAPI周りのツールが強力なため (特に[OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator))、 なかなか太刀打ちできないかなぁ...といったところです:cry:

## 今回想定するユースケース
「URLパラメータにxxxが含まれている場合のみ、レスポンスのこのフィールドは必ず存在する」といったケースです。
以下のような例を考えてみます。

- ユーザー情報を取得するAPIがある (ex: `GET /user/{id}`)
- レスポンスのフィールドに`gender`や`age`が存在するが、デフォルトでは返さないフィールドである
- これらはURLパラメータに明示的に指定された場合のみ、返すフィールドである (ex: `GET /user/1?attr=gender,age`)

## API仕様書での記述

### API Blueprintの場合
[API Blueprint](https://apiblueprint.org/documentation/specification.html#def-action-section)では、リクエスト × ステータスコード × レスポンスの3つの組み合わせを複数記述できるため、柔軟性があります。

```markdown
# Resource [/resource]
## Create Resource [POST]

+ request A
        ...

+ response 200
        ...

+ request B
        ...

+ response 200
        ...

+ response 500
        ...

+ request C
        ...

+ request D
        ...

+ response 200
        ...
```

しかし、記述量も多くなり複数のブロックに跨ってしまうため、柔軟過ぎると感じることもしばしば。

### OpenAPIの場合
[OpenAPI](https://swagger.io/specification/)ではリクエストの複数パターン記述ができないため、こういったユースケースを実現することは難しそうです。
`description`や`examples`に記載することで情報量はカバーできますが、実際の挙動との関連性を示すには少し弱いと感じます。

レスポンスについては、`discriminator`を使えば複数記述はできるようです。しかし、

> Adds support for polymorphism

とあるようにその概念に沿った使い方かというと、ちょっと違うかもしれません。

## テスティングツール

### dredd
https://dredd.org/en/latest/
API Blueprint向けメインとして用意されています。各言語でのhook操作も記述できたりと、フルスタックなツールです。
実はこのdredd、OpenAPIもサポートしているのですが、version3はexperimentalであるため現段階では実用的であるとは言えません...残念:sob:

### karate
同Adventカレンダー18日目の記事 [APIのE2Eは `カラテ` を使うべし。古事記にもそう書いてある。](https://hajimeni.hatenablog.jp/entry/2020/12/18/000100) でも紹介されているツールです。
[ドキュメント](https://intuit.github.io/karate/#schema-validation)にあるように、一応Schema Validationが可能ではあるようです。

```gherkin
* def oddSchema = { price: '#string', status: '#? _ < 3', ck: '##number', name: '#regex[0-9X]' }
* def isValidTime = read('time-validator.js')
When method get
Then match response ==
  """
  { 
    id: '#regex[0-9]+',
    count: '#number',
    odd: '#(oddSchema)',
    data: { 
      countryId: '#number', 
      countryName: '#string', 
      leagueName: '##string', 
      status: '#number? _ >= 0', 
      sportName: '#string',
      time: '#? isValidTime(_)'
    },
    odds: '#[] oddSchema'  
  }
  """
```

しかし、既にOpenAPIのyamlがあるのだから、そのファイルを使って検証したくはあります。
karateは細かなSchema Validation向けのツールではないと思うので、色々混ぜ込まないほうが良いのかもしれません。

### その他のツール
いくつか探してみましたが、OpenAPIのversion3に対応していて使いやすそうなツールは見つかりませんでした。ご存じの方居ましたら、ご教授願いたいです:bow:

目的は違いましたが、[prismのValidation Proxy](https://meta.stoplight.io/docs/prism/docs/guides/03-validation-proxy.md)は使えそうだなと感じました。

## まとめと所感
API仕様書にどこまで書くのが良いかについては現在も試行錯誤中ですが、あまり細かすぎることは書かないほうが良いかなと考えています。
ツール側の都合で仕様書のほうが変な制約を受ける、というのも正しくないでしょうし。
場合によっては、`externalDocs`等を使って棲み分けるほうが開発者にとっても理解しやすいのかもしれません。

API仕様書周りは久しぶりに時間を使って調べましたが、OpenAPIも機能が豊富で奥が深いですね。
個人的にdreddには愛着があるので応援...もとい、contributeしていけるように努力したいと思いました:muscle:

## 参考資料
- [本当に使ってよかったOpenAPI (Swagger) ツール](https://future-architect.github.io/articles/20191008/)
- [jsonschema/OpenAPI Specでのstrictなschema定義の大変さとPolicy as Codeについて](https://pod.hatenablog.com/entry/2019/03/02/200717)