SSKDsのバージョニング方法について考えてみた

この投稿は用語の説明と本題の二本立てになっています。

# APIバージョニングの要素、用語など

## LSUDs

* ***Large Set of Unknown Developers*** の頭字語
* 不特定多数のクライアントに公開されている、TwitterとかのAPIのことを言う
* 「不特定多数の開発者」と読めるのでソフトウェアの文脈にも適用できそうだが、登場するのはほぼAPIの文脈なのでAPIの事を指していると考えて差し支えない


## SSKDs

* ***Small Set of Known Developers*** の頭字語
* 特定少数のクライアントに対してのみ公開されている、社内システムなどのAPIのこと
* 「特定少数の開発者」と読めるのでソフトウェアの文脈にも適用できそうだが、登場するのはほぼAPIの文脈なのでAPIの事を指していると考えて差し支えない

## 後方互換性

以下のようにメンバーを増やす場合は、idなどの元々存在していたメンバーへの参照性が失われたわけではなく、全ての機能が従来通り使えるので「後方互換性がある」と言う。

```json:変更前のレスポンスJSON
{
  "id": 1,
  "name": "hoge",
  "age": 80
}
```

```json:変更後のレスポンスJSON
{
  "id": 1,
  "name": "hoge",
  "age": 80,
  "job": "engineer"
}
```

## 破壊的変更

以下のように元々レスポンスJSONの中に存在していたageを削除してしまうと、そのageを参照し活用しているクライアントが正常に機能しなくなってしまう。
これを破壊的変更という。「後方互換性が無い」とも言う。
また、「あるクライアントから見ると破壊的であるが、別のクライアントから見ると変更になった箇所を利用していないので影響が無く破壊的ではない」と言う考え方も出来る（APIをどの観点から見るかで破壊的変更かどうかが異なると言う事）。

```json:変更前のレスポンスJSON
{
  "id": 1,
  "name": "hoge",
  "age": 80
}
```

```json:変更後のレスポンスJSON
{
  "id": 1,
  "name": "hoge"
}
```

ageのinteger型をstringに変更するような、valueの型が変更になることも破壊的変更という。

```json:変更前のレスポンスJSON
{
  "id": 1,
  "name": "hoge",
  "age": 80
}
```

```json:変更後のレスポンスJSON
{
  "id": 1,
  "name": "hoge",
  "age": "80"
}
```

## バージョンを含める箇所

以下のようにリクエストに関係する要素のどこかしらにバージョンを含める。
メジャーと思われる順番で列挙

1. URIにバージョンを含める
2. クエリパラメータに含める
3. HTTPヘッダーのmedia_typeに含める

```URIにバージョンを含める
api/v2/hoge
```

```クエリパラメータに含める
api/hoge?version=1
```

```HTTPヘッダーのmedia_typeに含める
content-type: application/vnd.github.v3+json # githubの例
```



## バージョニングの規則

最もメジャーな手法はv1から初めてv2, v4のように上げていく規則。特にこれといった名前や有名な提唱や定義があるわけではなく、自然と利用され定着している。

次に良く出てくるのがセマンティックバージョニングという有名なバージョニング規則。APIよりもソフトウェアのバージョニングで使われているのを目にすることが多いはず。APIではメジャー.マイナー.パッチみたいなバージョニングは珍しいかもしれない。こちらは規則が細かく定義されている。

他にはカレンダーバージョニングという、セマンティックバージョニングと同じように細かく定義された規則があるが、こちらはAPIに於いてはセマンティックバージョニングよりも馴染みが無い。

セマンティックバージョニング参考: [セマンティック バージョニング 2.0.0](https://semver.org/lang/ja/)
カレンダーバージョニング参考: [CalVer Timely Project Versioning](https://calver.org/)




# 本題: SSKDsにおけるAPIバージョニングを考えてみた

SSKDsを対象にバージョニングを考える機会があり、セマンティックバージョンやインクリメントする手法ではデメリットが大きいことがわかった。

それを回避するためのバージョニング方法についても考えてみたので参考になれば幸いです。


## そもそも何故バージョニングをしたいか（どういうケースなのか）

複数地域で提供しているサービスにおいて、ある一部の地域で先行して新機能をリリースする事になり、内部システムのAPI（SSKDs）も新旧の振る舞いを同時に提供する必要が出てきた、というケース。

## バージョンを上げる手法のデメリット

APIのバージョンを上げる際に、影響の無いクライアントからするとただ単にAPIのバージョンを上げるだけの改修コストが発生する。

新旧の両バージョンを運用しないのであれば「バージョンは上げずに振る舞いだけ変えてくれ」という話になりがちで、それはそれでまあ問題無い。

今回設定したケースのように新旧両バージョンを運用する場合が問題で、影響無いタイプのクライアントは「じゃあ古いの使うわ。いつか上げるわ」となりがち。

さらに、そういうクライアントが沢山居ると、時間が経つごとに「誰がどのバージョンを使っているのか」の把握が困難になってくる。毎回確認したり齟齬が生まれたりするような、開発速度を低下させる要因になりえる。

旧バージョンの利用期限を設けて移行を促すにしても、本質的ではない改修のためのコストを支払う事には変わりは無い。

上記は同一エンドポイントで起きる話だが、エンドポイントごとにバージョンを持つ場合はそもそもそのポリシー自体が「このエンドポイント今幾つだっけ」という面倒な会話が発生するリスクを抱えており、それを避けるために全エンドポイントの最新バージョンを揃えるようなポリシーにしてしまうと、APIの新バージョンリリースを機に、影響の無いクライアントが一気に古いバージョンを使うという状況になってしまう。

以下デメリットまとめ

* 影響無いタイプのクライアントにとってはバージョンアップは無駄なコストで、そのタイプのクライアントが多ければ多いほど無視できないコストになってくる
* 同一エンドポイントで複数バージョンが乱立する可能性がありコストが上がる

## 解決策案: 旧振る舞いのバージョンをカレンダーバージョニングで新規発行する

既存エンドポイント: `api/hoge/`があったとして、

* 旧振る舞いを提供する`api/hoge/202006/`と言うエンドポイントを新しく発行する
  * 改修後の新振る舞いエンドポイントは `api/hoge/`で、エンドポイント自体は変更無しだが振る舞いは変わっている
  * 旧振る舞いエンドポイント`api/hoge/202006/`はエンドポイントは新しいが振る舞いは既存と一緒

* 旧振る舞いを使いたいクライアントが、旧振る舞いエンドポイントに向け先を変更する

この方法であれば、既存のエンドポイントがそのまま新振る舞いとなり、影響の無いタイプのクライアントは改修せずにそのまま使い続ける事ができる。

「バージョン上げられるにも関わらずコストを嫌って旧バージョンを使うクライアント」が居なくなる＝同一エンドポイントで複数バージョンが乱立する可能性が減る（ただしゼロでは無い）。

また、複数エンドポイント間で最新バージョンが異なるという事は起き得ない。

### Q. この方法だと遅れているクライアントの方が多いケースでは逆に総コスト上がるんじゃない?

確かにその通り。その場合はこの手法は採らない方が全体の総コストは抑えられると思う。


# まとめ
実を言うと、当初は汎用的なバージョニング方法を考えるつもりだったんですが、APIのバージョニングでは色んな状況が考えられるため、結果として限定されたケースでの手法となってしまった感があります。ただ、割と遭遇するケースのような気もしていたり。
いづれにしても、「複数バージョンってきついよな！」と言うピュアな気持ちを大切にしたいですね（勝手にしろ）。