2024年12月02日

APIバージョン管理への取り組み方

本エントリはKong Advent Calendar 2024に寄稿しています。

APIは作る側と使う側の取り決め(コントラクト)です。API仕様は作る側が定義するものですが、そのAPIは使う側にとっては重要な依存関係であり、APIの仕様通りサービスが提供される事が極めて重要です。同時に、サービスは使う側のニーズを満たす為に継続的に改善されていかなければいけません。変わり続けるAPIに対して、どのように安定的にサービスを提供し続けるのか?APIのバージョン管理は、使う側の信頼を保つ上で重要な要素となります。

本エントリでは、APIバージョン管理が重要な背景、ベストプラクティス、そしてKong Gatewayを利用した実践的なバージョン管理の手法について説明します。

APIバージョン管理の背景

作る側(API提供者)の背景

作り手側にとって、複数バージョンをサポートする事は容易ではありません。複数バージョンの管理は、同じ修正を複数箇所に適用する必要があるという事実と向き合う事を意味します。これはソースコード管理に何を使おうが、どのような開発手法を用いようが変わりません。トランクベースの開発も同様で、リリースはPull Requestと比べるとかなり長い期間維持されるブランチであり、何かしらの方法でトランクに適用されたコードを各リリースブランチにも反映させる必要があります。

また、この同期を安全かつ自動で行う方法はありません。修正をブランチで行わないといったプラクティスや、自動化によってそのタスク負荷とリスクを下げるアプローチはありますが、あくまで改善策であり物理的にトランクの修正から適切なものだけをリリースブランチに反映するというタスク自体は変わりません。

Trunk Based Development – Branch for Release” – Trunk Based Development, trunkbaseddevelopment.com

一方サービスは利用者にとって価値提供をし続ける事によって存続する事が出来ます。これは利用者の新たなニーズに対応する事であり、サービスが常に変更し続ける事を意味します。有益なサービスは、それだけ多くの利用者に対して、多くの機能改善を提供するサービスでもあります。一方、多くの機能改善を提供しようとすると、時として下位互換性の無い変更の導入を余儀なくされる事もあり、この場合新旧2つのバージョンを提供する必要が出てきます。

使う側(API利用者)の背景

API利用者のニーズは多種多様です。場合によってはそのサービスの成長に対する依存関係が強く、より自分たちのニーズにあった改修を多く要求します。モバイルアプリ等、エンドユーザーが多くリリースサイクルの短いアプリによく見られる傾向です。一方、APIの利用は必要に駆られた統合のためであり、特定のデータにアクセスする事が目的の場合、APIに対して求める事は多くありません。むしろ統合部分であるAPIへの変更は邪魔であり、可能な限り現行のAPIを利用し続ける傾向があります。

…このように、APIにも他のソフトウェアコンポーネントと同じようにバージョンはあるが、その実装のバージョンとの相関はない。APIのバージョンは、実装がどのように進化するかではなく、インターフェイスコントラクトに対する変更(コンシューマーから見える変更)に基づいて進化する。

… セマンティックバージョニングをAPIに適用した時の数字は、<破壊的>.<非破壊的>の2つだけである。 … コンシューマに必要なのは、1つ目の破壊的なレベルの数字だけである。

Web APIの設計”, Arnaud Lauret, 2020, 翔泳社.

上記のような思想は極端に思われるかも知れませんが、特定APIに対して何を求めるかは千差万別であり、変化を喜ばない利用者もいるという事実を知っておくことも重要です。

APIバージョン管理の基礎

セマンティックバージョニング

セマンティックバージョニングは、ソフトウェアリリースにおけるバージョン指定を明文化する目的で策定された仕様です。APIではなく、ソフトウェアアーティファクト(ビルド)や、そのコード管理ブランチの命名規則として標準的なものです。Semantic Versioningを短くSemVerと呼ぶ事もあります。

A Guide to Semantic Versioning” – Baeldung, baeldung.com.

APIにおいては、下位互換性の無い変更を導入する際にバージョンを変更する必要がある為、セマンティックバージョニングにおけるメジャーリリース単位でAPIバージョンも変わります。つまり図におけるX(メジャーバージョン)がArnaud Lauretの言うところの『破壊的な変更』を指します。

API仕様における互換性

一般的に、ソフトウェアのメジャーバージョンはAPIのバージョンと同期しますが、厳密にはソフトウェアのバージョンとAPIのバージョンの間に直接的な関連性はありません。APIはコントラクトであり、その互換性判定はむしろスキーマ(DB等)の互換性判定と近いものです。

以下のような変更がAPIに発生する場合、これらは互換性の無い変更となります:

  • 必須項目の追加/変更/削除
  • 任意項目の変更/削除
  • 任意項目の必須項目への昇格

つまり、追加機能の為に必要な任意の項目をいくつ追加しても互換性は失われません。

バージョンのライフサイクル

API提供者にとって、同時に提供するバージョンを可能な限り少なく留めることが重要です。バージョン数が少なければ少ないほど、より多くのリソースを価値提供に充てることが出来ます。

前述したとおり、複数ブランチを運用する場合、原則修正はトランクに対してのみ行い、これら修正は個別に各ブランチにも反映させる必要があります。バージョンに依存しない共有機能のバグ修正、利用するランタイムやライブラリのセキュリティパッチ適用等、そのバージョンをサポートする限り原則全て取り込む必要があります。

当然、対象となるバージョンの数が多いとこれら対応にかかる工数は増大します。単体テストやAPIテストである程度の充実していれば、例えばライブラリのバージョンアップ等はさほど大きな労力を必要としません。一方、修正の中には共通的な処理の変更等が発生するものもあります。この場合トランクへの修正は、まだ全てのバージョンに反映されていないコードに対する変更も含みます。つまり場合によってはコミット単位で取り込む事も困難な場合もあります。

API提供者にとっては、下位互換性の無い変更を減らしつつ、かつ古いバージョンはなるべく早く提供停止する事が重要です。

APIのDeprecation(非推奨)とDecommission(提供停止)

一方API利用者のニーズは様々で、提供停止を望まない利用者も多く存在します。これら利用者にとって、今使っているバージョンが提供停止となること、それに伴い新しいバージョンへの対応を強要されることは負担でしかありません。特に社内で利用されるAPIに発生しがちであり、問題が顕著化しやすいです。

ここはAPI提供者とAPI利用者の利害が真っ向から衝突する可能性がある領域です。 API提供者にとって利用者のニーズは重要ですが、APIの提供する価値はどれだけ機能改善等にリソースを割けるかにも大きく影響します。場合によっては特定API利用者の要望によって古いバージョンをより長い期間提供する事もあると思います。ただ基本方針は『いかに利用者の提供停止負荷を下げるか』となります。

Deprecation(非推奨)→ Decommission(提供停止)というアプローチを取ります。つまりAPIの特定オペレーションに対し、まずは将来的に利用負荷となる事を明示し、指定期間後に提供廃止とする事です。このアプローチ最大の目的はAPI利用者との期待値を合わせ、移行に向けた計画を実行する上で十分な期間を提供する事です。

  • Deprecationは大々的に通達し、影響、および利用停止となるタイミングを明記する。(一般的には半年前)
  • Decommission前に複数回(90日前/30日前/15日前/7日前、等)通知する。
  • Deprecationへの対応方法をドキュメント化する。

これらは一般的なAPIのDeprecation→Decommissionのガイドラインであり、API利用者の期待値と近いポリシーです。一方これだけで十分で無い場合もあり、APIの種類やAPI利用者の数/層、提供停止の影響度合い等によって別途対応が必要なケースも出てきます。

APIバージョンの指定方法

APIのバージョンをどの様に指定するかにはいくつか選択肢があります。REST APIのバージョニングについて書籍で言及している最も古いものはおそらくRESTful Web Servicesですが、ここではURIによってバージョンを切り分ける手法について少しだけ触れられています。またこれが現在最も一般的な指定方法です。具体的には/v1/users/lonardrの様に、リソース名の前にバージョンを埋め込みます。(Leonard Richardson & Sam Ruby, “RESTFul Web Services”, O’Reilly Media, 2007, p.235.)

一般的には他にも選択肢はあり、ユースケースによってはこれらの方が妥当な場合はあります:

  • クエリパラメータでのバージョン指定:example.com/products?version=v1
  • ヘッダによるバージョン指定:version: v1

他にもコンテキストを意識した(セッションを元にした)指定や、ボディの属性に指定する方法等もありますが、RESTの概念からはやや離れたアプローチであり一般的ではありません。

Kong Gatewayにおけるバージョン指定

Kong Gatewayでは、接続先サービスだけでなく、そのリソースにアクセスする為のパス情報もRouteというエンティティとして管理されます。この為、同一サービスに対して複数のパスを定義する事も、それぞれのパスに異なるポリシーを適用する事も可能です。

例えば、利用ユーザがアクセス出来るRoute毎に制限を変えるユースケースです。同一サービスに対して/standard/premiumという異なるRouteを用意し、/standardのRouteのみに流量制限のポリシーを指定する。これにより、プレミアムユーザーとして認可を受けたユーザのみ無制限で、それ以外のユーザには特定量のリクエスト数のみ許容するような切り分けを行うことが出来ます。

複数のバージョン指定方法を同居させる

Kong Gatewayの場合、この柔軟性を利用して同一のサービスに対して複数のバージョン指定を行う事も可能です。

上記はKong GatewayにおけるRouteの定義画面です。ここではパスのみを指定する事も出来ますが、パスとヘッダを合わせて指定する事も可能です。v1とv2を異なるRouteとして指定するとURIによるバージョン指定が可能で、同一パスに対して異なるヘッダを指定するとヘッダによるバージョン指定が可能となります。

さらには、これら2つとも同時に指定すると、同じサービスに対して異なるバージョン指定方法のRouteを指定する事も可能です。以下がその指定例ですが:

この定義によりAPI利用者側からは2つの異なる方法でバージョン指定が出来る様になります。

URIHeader接続先
www.sample.com/v1/echoEcho V1 Service
www.sample.com/v2/echoEcho V2 Service
www.sample.com/echoversion: v1Echo V1 Service
www.sample.com/echoversion: v2Echo V2 Service

まとめ

APIのバージョン管理は、APIのライフサイクル、API提供者とAPI利用者のニーズや課題も考えなくてはならない難しいタスクです。いかに相応のバランスを取るのか、そしていかに期待値を合わせてAPI利用者にとっての負荷を下げるかが重要です。

また、実践的なアプローチとしてKong Gatewayの柔軟なRoute設定の仕組みや、複数のバージョン指定方法を同居させる方法についても説明しました。Kong Gatewayを利用すると、サービスのデプロイとルーティングの定義を切り離した、より柔軟なトラフィック制御のアプローチを取ることが出来ます。

橋谷信一

Kong株式会社 スタッフソリューションエンジニア