API のバージョン管理をめぐる議論は、たいてい間違った側から始まります。バージョン番号をどこに置くか、という話です。実のところ、それはこの主題のなかでもっとも結果を左右しない決定です。本当に重要なのは、そもそもどの変更が新しいバージョンを必要とするのかであり、ほとんどのチームはここを、油断する方向に読み違えます。純粋な追加にすぎないと信じたものを出荷し、どこかのクライアントが壊れるのです。
使える考え方はこうです。あなたの API は、呼び出す側が何を当てにしてよいかについての約束にほかなりません。まっとうな呼び出し側が当てにしていたものを無効にしてしまうなら、その変更は破壊的です。そして呼び出し側は、ドキュメントが明示的に許している範囲よりも、はるかに多くのことを当てにしています。
誰もが引っかかる変更: レスポンスにフィールドを一つ追加することです。それは追加でしかなく、きちんと書かれたクライアントを壊すことはできず、それでも実際のクライアントを定期的に壊します。一部のクライアントがレスポンスを厳密に検証し、知らないフィールドを拒否するからです。それがあなたの落ち度なのか相手の落ち度なのかは、相手の連携が止まり、電話が鳴り出した時点でどうでもよい話になります。
本当に破壊的なのはどの変更か
議論の余地なく破壊的: フィールドの削除や名前の変更、フィールドの型の変更、必須のリクエストパラメータの追加、検証の厳格化、既存の値の意味の変更、そして呼び出し側が分岐に使っているステータスコードの変更です。
安全に見えて実際には破壊的: クライアントが厳密に検証しているところへ、レスポンスにフィールドを追加すること。約束したわけではないのに呼び出し側が前提にしていた配列の順序を変えること。誰かが文字列として突き合わせているエラーメッセージを書き換えること。同期の処理を非同期に変えること。
本当に安全: 省略可能なリクエストパラメータを新しく足すこと、新しいエンドポイントを足すこと、検証を緩めること、そして列挙に新しい値を足すこと。ただし最後の一つは、知らない値が来ることを最初からクライアントに伝えてあり、実際にそれを処理できていると確認できる場合に限ります。
ここから見えてくるのは、安全かどうかは仕様が何を許しているかではなく、呼び出し側が実際に何をしているかで決まる、ということです。すべてのクライアントを自分たちで握っているなら、確認する手段があります。そうでないなら、些細だと思っていたところに誰かが寄りかかっていると考えてください。実際にそうなっているからです。
バージョン管理の方式と、それぞれの代償
URL にバージョンを置く。 もっとも一般的なやり方で、その美点は誰の目にも明らかだという点にあります。/v1/orders と /v2/orders は、見た目からして別のリソースです。代償は、API 全体のバージョンを一気に上げる形になりやすいことです。一つのエンドポイントだけの変更が残り全部を巻き込み、クライアントは一度にまとめて移行させられます。
ヘッダーにバージョンを置く。 URL を安定させたまま、より細かい粒度で刻めますが、その代わり目に見えません。意識して探さないかぎり、ブラウザでもログの一行でもバージョンは見えず、ヘッダーを省いた呼び出し側にはあなたが決めた既定値が返ります。その既定値をどうするかは、意図をもって決めておくべき事柄です。
日付ベースのバージョン管理。 呼び出し側が日付を固定すると、その時点の挙動そのままの API を受け取ります。Stripe はこの方式を文書化しており、アカウントごとに既定のバージョンがあり、リクエスト単位でそれを上書きできます。移行の刻み幅を可能なかぎり小さくできる代わりに、互換性の負担は自分たちのコードベースへ移ります。そのコードベースが、バージョン間の変換をこれからずっと保守することになります。
バージョンを設けず、追加だけで済ませる。 何一つ取り除かないと本気で約束できるなら、十分に成り立つし、過小評価されている選択肢です。代償は、消せないフィールドが積み上がり、直せない挙動が残ることです。急に届く請求書ではなく、ゆっくり効いてくる税金のようなものだと考えてください。
正解はなく、あるのは自分たちの移行負担と顧客の移行負担のあいだの取引だけです。日付ベースは呼び出し側にもっともやさしく、運用にもっとも金がかかります。URL でのバージョン管理はちょうどその逆になります。
破壊的変更を耐えられるものにする
まず広げ、あとで狭める。 新しいフィールドを古いフィールドの隣に足し、両方を埋め、クライアントに移る時間を与え、古いほうは後のバージョンで取り除きます。これで一つの破壊的変更が二つの安全な変更に変わり、この一手間はほとんどの場合に見合います。
誰が何を使っているかを計測する。 誰がまだそのバージョンに乗っているのかを知らないまま、バージョンを安全に終了させることはできません。リクエストごとにバージョンとクライアントの識別子を記録してください。そうすれば提供終了が、闇に向かって告知を投げる作業ではなく、根拠に基づいた会話になります。
メールだけでなく、仕組みで知らせる。 Deprecation と Sunset のレスポンスヘッダーを使えば、クライアントは終了日をプログラムから見つけられます。もう誰も読んでいないアドレスへ送られたメッセージより、はるかに気づかれやすい伝え方です。
現実的な猶予を与える。 連携を保守しているのは、別の優先順位を抱えた人たちです。相手のリリースサイクルより短い期限は、ただ見送られて終わります。公開 API なら六か月が一般的で、直接話をしたことのある少数の既知のパートナーが相手なら、それより短くても筋は通ります。
古いバージョンを終了させる
これを可能にしているのは、先ほどの計測です。日付を告知し、トラフィックが減っていく様子を見守り、まだ移っていない呼び出し側には名指しで連絡してください。
長い尾を覚悟してください。顧客側の誰も自分たちのものだと覚えていない連携が必ず存在し、それはあなたがバージョンを止めた瞬間に見つかります。ここで効くのがブラウンアウトです。最終日より前に、あらかじめ告知した時間帯だけ古いバージョンを短く止めておくと、障害は誰かがそれを待ち構えているあいだに起き、繁忙期の真ん中では起きません。
正直に言えば、一部の呼び出し側は、古いバージョンが動かなくなって初めて動きます。驚くのではなく、そうなる前提で計画してください。そして失敗したときに返るのがタイムアウトではなく、何が起きたのかを説明する明確なエラーになるようにしておいてください。
必要のないバージョン
社内向けの API のほとんどは、そもそもバージョン管理を必要としません。呼び出し側をすべて自分たちで握っており、両側を同時に変えられるからです。自分たちのサービス二つが使っているだけのインターフェースにバージョン交渉を足すのは、費用はかかるのに何からも守ってくれない機構です。
必要になる分かれ目は、すべての利用者を同時にデプロイできなくなった時点です。相手が別のチームでも、別のリリースサイクルでも、別の会社でも同じことです。そこが本当の引き金であり、API がどれだけ公開されているかとは関係ありません。サーバーレス API の解説にある設計上の勘所は、エッジでもそのまま当てはまります。
Mecanik はソフトウェア開発の一環として、この種の API を設計し、保守しています。バージョンの付け方そのものが面白い決定であることはめったにありません。どの呼び出し側がまだ古いほうに残っているのかを知ることは、いつでも面白い問題です。
よくある質問
破壊的な API 変更とは何ですか。 フィールドの削除や名前の変更、フィールドの型の変更、必須パラメータの追加、検証の厳格化、値の意味の変更、そして呼び出し側が分岐に使っているステータスコードの変更です。実際には破壊的になるものとして、クライアントが厳密に検証しているときのレスポンスへのフィールド追加、呼び出し側が前提にしていた配列順序の変更、誰かが突き合わせているエラーメッセージの文言の変更もあります。
API のバージョンは URL とヘッダーのどちらに入れるべきですか。 URL でのバージョン管理は目に見えて単純ですが、API 全体のバージョンを上げる方向に働き、クライアントに一度で全部を移行させます。ヘッダーでのバージョン管理は URL を安定させ細かい粒度を許しますが、ログでもブラウザでも見えず、ヘッダーを省いた呼び出し側のための既定値を意図して決める必要があります。どちらも間違いではなく、自分たちの移行負担と顧客の移行負担を交換しているだけです。
日付ベースの API バージョン管理とは何ですか。 呼び出し側が日付を固定し、その時点の挙動そのままの API を受け取る方式で、Stripe がアカウントごとの既定バージョンを持たせ、個々のリクエストで上書きできるようにしているのがその例です。クライアントにとって移行の刻み幅を可能なかぎり小さくできる一方、互換性の負担は自分たちのコードベースへ移り、そこがバージョン間の変換を保守し続けることになります。
API の提供終了までの猶予はどれくらい必要ですか。 公開 API なら六か月が一般的で、直接話をしたことのある少数の既知のパートナーが相手なら、それより短くても筋は通ります。連携を保守しているのは別の優先順位を抱えた人たちなので、相手のリリースサイクルより短い猶予は、どれだけ明確に告知しても見送られて終わります。
社内 API にもバージョン管理は必要ですか。 たいていは不要です。呼び出し側をすべて自分たちで握っており、両側を同時に変えられるからです。バージョン管理が必要になるのは、すべての利用者を同時にデプロイできなくなった時点で、相手が別のチームでも、別のリリースサイクルでも、別の会社でも同じです。引き金はそこであって、API が公開かどうかではありません。
コメント