クライアントアプリを壊さないAPI変更とリリース順序の考え方
この記事で扱う問題
Web のフロントエンドとサーバーを同時にデプロイできるなら、API の形を変えても大きな問題にはなりません。しかし、モバイルアプリのように利用者が古いバージョンを使い続けるクライアントがある場合は事情が違います。
- レスポンスの項目名を変えた → 古いアプリで画面が表示されない
- 必須の入力項目を追加した → 古いアプリからのリクエストがすべて失敗する
- 列挙値に新しい値を足した → 古いアプリが知らない値で落ちる
しかも、サーバーを戻しても、すでに新しい形式で保存されたデータは戻りません。
判断
API を変更するときは、次の原則で進めます。
- 追加はしてよいが、削除・名前変更・意味の変更はしない。ただし「追加してよい」のは、クライアントが知らない項目を無視する作りになっている場合に限ります。未知の項目を拒否する厳格なデシリアライザや、
additionalProperties: falseのような契約を使っているクライアントでは、レスポンスに項目を足すだけで失敗します。最初に、クライアントが未知の項目を許容するかを確かめます - 新しい必須項目は、まず任意項目として追加し、未指定のときの既定の振る舞いを決める
- 列挙値を増やす場合は、古いクライアントが知らない値をどう扱うかを先に確かめる
- 形が大きく変わるなら、エンドポイントやバージョンを分け、古いものは利用状況を見て段階的に廃止する
リリースは、順序を分けて考えます。
- サーバー側で、新旧どちらの形式も受け付けられるようにする(まだ新しい形式は使われない)
- クライアントを新しい形式に切り替える
- 古い形式の利用がなくなったのを確かめてから、古い形式の処理を消す
DB のカラム変更も同じで、段階を分けます。
- 新しいカラムを追加する
- 書き込みのたびに、古いカラムと新しいカラムの両方に書く
- 既存の行のデータを新しいカラムに移し(バックフィル)、すべて移し終えたことを確かめる
- 読み先を新しいカラムに切り替える
- 古いカラムへの書き込みをやめる。その前に、古いカラムを読むコードがすべてのサーバーから消えたこと、切り戻す可能性のある期間が過ぎたことを確かめる
- 古いカラムを消す
3 を飛ばすと、両方に書き始める前からある行は新しいカラムが空のままで、読み先を切り替えた時点でデータが欠けて見えます。
5 を飛ばして古いカラムを消すと、まだ両方に書いているコードが、消えたカラムへの書き込みで失敗します。また、古いカラムを消した時点で、古いバージョンのコードへは切り戻せなくなります。4 までは、いつでも1つ前に戻せる状態を保てます。
なぜそう判断したか
互換性を壊す変更で一番怖いのは、ロールバックできなくなることです。
各段階で「この時点で1つ前に戻しても壊れないか」を確かめながら進めれば、どこで問題が起きても戻せます。一度にまとめて変更すると、戻したときに新旧どちらの形式にも合わない状態が生まれます。
レビューでも、「このリリースは、どのデプロイ順序を前提にしているか」「戻したときにデータは読めるか」を確認します。担当外のクライアントや、別チームのデプロイ予定まで確認が必要になることも多いです。
どこから設計過剰になるか
- クライアントとサーバーを必ず同時にデプロイでき、古いクライアントが存在しないなら、段階を分ける必要はありません
- 利用者が社内の数人だけの API なら、事前に連絡して一斉に切り替えるほうが早いです
- すべての変更にバージョン番号を付けると、管理するバージョンが増えすぎます。互換性を保てる変更は、バージョンを変えずに追加で済ませます
まとめ
- 古いクライアントが残る API では、削除・名前変更・意味の変更はしない。追加も、クライアントが未知の項目を許容する場合に限る
- 「サーバーが新旧を受け付ける → クライアントを切り替える → 古い形式を消す」の順にリリースする
- 各段階で「1つ前に戻しても壊れないか」を確かめる