- ソフトウェアエンジニアリングにおいてAPIは中核的なツールであり、優れたAPIは退屈なくらい親しみやすくシンプルであることが望ましい特徴
- APIは一度公開すると変更が難しいため、ユーザー環境を壊さない原則(WE DO NOT BREAK USERSPACE) が重要
- やむを得ず変更する場合は バージョン管理(versioning) が必要だが、これは複雑さと保守コストを大きく増加させる必要悪
- APIの品質は結局 プロダクト自体の価値 に依存しており、設計の悪いプロダクトでは良いAPIを作るのが難しい
- 安定性と拡張性のために APIキーベース認証、冪等性(idempotency)、レートリミット、カーソルベースのページネーション などを考慮すべき
序論: API設計の重要性と文脈
- 現代のソフトウェアエンジニアの主要業務の一つは APIと相互作用 すること
- 著者自身もREST、GraphQL、コマンドラインツールなど、さまざまな形の公開および社内向けAPIを設計・実装・活用してきた経験を持つ
- 既存のAPI設計アドバイスは、複雑な概念(RESTの定義、HATEOASなど)に執着する傾向がある
- 本稿は実体験に基づいて 実用的なAPI設計原則 を整理したもの
親しみやすさと柔軟性のバランス: 良いAPIの第一条件
- 良いAPIは「平凡で退屈な」API、つまり既存のAPIと使い方が似ているべき
- ユーザーは APIそのものより自分の目的達成に集中 しているため、参入障壁の低い設計が必要
- 一度公開されたAPIは 変更が非常に難しく、最初の設計段階で慎重さが求められる
- 開発者はできるだけ簡潔なAPIを望む一方で、長期的な柔軟性を残すための悩みが常につきまとう
- 結果として、親しみやすさと長期的柔軟性のバランス が核心的な課題
ユーザー空間を決して壊さない(WE DO NOT BREAK USERSPACE)
- 既存のレスポンス構造に フィールドを追加 する変更は、ほとんどの場合問題ない
- しかし フィールドの削除、型や構造の変更 は、すべての利用側コードを壊す結果を招く
- APIの保守者には、既存ユーザーのソフトウェアを意図的に壊さない 責任 がある
- HTTPの
referer ヘッダーのスペルミスすら修正しない理由は、ユーザー空間を保全する文化 にある
APIを壊さずに変更する: バージョン管理戦略
- 本当に必要な場合にのみ APIへの破壊的変更 を認め、その際は バージョン管理 が正解
- 旧バージョンと新バージョンを 同時に運用しながら段階的な移行 を促すべき
- バージョン識別子はURL(
/v1/)やヘッダーなど、さまざまな方式で利用でき、ユーザーは各自の速度で移行できる
- バージョン管理には 莫大な保守コスト(エンドポイント増加、テスト、サポート)と ユーザーの混乱 という欠点がある
- Stripeのように内部に変換レイヤーを置いても、根本的な複雑さは避けられない
- APIのバージョン管理導入は最終手段 であるべき
APIの成功要因は全面的にプロダクト価値にかかっている
- APIは本質的に 実際のビジネスプロダクトのインターフェースにすぎない
- OpenAIやTwilioのAPIでも、結局ユーザーが求めていたのは APIが提供する機能そのもの
- 価値あるプロダクトなら、APIが使いにくくても利用される
- API品質は「マージン」の特性であり、本質的な競争力が近いときにだけ選択要因になる
- 一方で、そもそもAPIが存在しないプロダクトは技術ユーザーにとって大きな障壁になる
プロダクト設計が悪ければAPIも良くなりえない
- 技術的に完成度の高いAPI があっても、市場性のないプロダクト なら意味がない
- さらに重要なのは、基本的なリソース構造が非論理的または非効率的であれば、それはAPIにも現れる こと
- たとえばコメントをリンクドリストで保存するシステムでは、RESTfulな設計さえ自然に組みにくくなる
- UIでは隠せる技術的問題も、APIではすべて露出し、ユーザーにシステム理解を不必要に強いることになる
認証(Authentication)とユーザーの多様性
- 長寿命のAPIキーベース認証 を必ずサポートすべき
- OAuthのようなより安全な方式を追加でサポートするとしても、APIキーの 参入障壁は圧倒的に低い
- APIの利用者はエンジニアだけでなく、非開発者(営業、企画、学生、趣味開発者など)も多い
- 難しい、または複雑な認証要件(OAuthなど)は 非専門ユーザーにとって障壁 になる
冪等性(Idempotency)とリトライ処理
- アクション系リクエスト(例: 決済、状態変更など)は、失敗時の リトライ(retry) に対する安全性が重要
- 冪等性とは、同じリクエストを複数回送っても結果が一度しか処理されないことを保証 すること
- 標準的な方法は「冪等性キー」をパラメータまたはヘッダーで渡して重複処理を防ぐこと
- 冪等性キーの保存にはRedisなどの 単純なキー/バリューストア で十分であり、多くの場合は定期的な有効期限切れを適用しても問題ない
- 読み取り/削除リクエスト(REST方式)には通常必要ない
APIの安全性とレート制限(Rate limiting)
- コード経由のAPIリクエストはユーザー操作よりはるかに高速で発生 しうる
- 何気なく公開した1つのAPIが、意図しない形(例: 大規模チャットシステム)で利用されることもある
- レート制限(ratelimit)は必須 であり、高コストな処理にはより厳しく適用すべき
- 特定顧客に対する一時的なAPI無効化(killswitch)も選択肢として考慮すべき
- レスポンスヘッダー(
X-Limit-Remaining, Retry-After など)でレート制限情報を案内すべき
ページング(Pagination)戦略
- 大規模データセット(例: 数百万件のチケット)を効率的に返すには ページングが必須
- オフセットベース(Offset-based)のページングは簡単だが、大量データでは次第に遅くなる
- カーソルベース(Cursor-based)のページング は、クエリ性能を落とさず非常に大きなデータセットにも有効
- カーソルベースは実装と利用がやや難しいが、長期的には不可欠な変化になる可能性が高い
- レスポンスに
next_page フィールドなどを含め、次のリクエスト用カーソルを明確に案内するのが賢明
選択的フィールドとGraphQLについての見解
- コストが高い、または遅いフィールドはデフォルトのレスポンスから除外し、必要時のみ選択的に追加 すべき
includes パラメータなどで関連データを含められる
- GraphQLにはデータ構造の柔軟性という利点があるが、非開発者のアクセス性低下、キャッシュ/エッジケースの複雑化、バックエンド実装難易度 などの問題がある
- 実務経験上、GraphQLの導入は 本当に必要な場合に限る のが適切
内部向けAPIの特徴
- 社内APIは外部向けAPI(公開API)とは多くの条件が異なる
- 利用者の大半は 専門のソフトウェアエンジニア であるため、より複雑な認証や破壊的変更も可能
- それでも、冪等性、事故防止、運用負荷最小化 のための設計原則は有効
要約
- APIは変更しにくく、使うのは簡単であるべき という特性を持つ
- ユーザー空間を壊さないこと がAPI保守者の最も重要な義務
- APIのバージョン管理はコストが大きいため最終手段 としてのみ使うべき
- 最終的に APIの品質はプロダクトの本質的価値に左右される
- 設計の悪いプロダクトはAPIレベルで補っても限界が大きい
- シンプルな認証方式のサポート、必須アクションリクエストへの 冪等性、そして レート制限/ページングなどの安定化策 が重要
- 内部APIは用途と対象に応じて戦略が異なるが、設計の慎重さは依然として必要
- REST、JSONなどのフォーマットやOpenAPIなどは本質的な論点ではない。明確なドキュメント化のほうが重要
1件のコメント
Hacker Newsの意見
「userspaceを絶対に壊すな」という助言は有名だが、実はその反対側の側面についてもうまく触れられている。つまり、「カーネルAPIは予告なく壊れうる」ということだ。重要なのは「すべてのAPIをむやみに壊すな」ではなく、「安定性を宣言した部分だけは絶対に壊すな」という微妙なバランスである
Linuxカーネルがuserspaceを壊さないとしても、GNU libcはかなり頻繁にuserspace互換性を壊す。だから結果として、Linuxのユーザー空間はカーネル開発者がどれだけ努力しても頻繁に壊れてしまう。新しいバージョンのlibcでビルドされたプログラムやライブラリは、古いlibcでは正常に動かないこともあり、結局すべての構成要素を一度にアップグレードしなければならないのが実情だ。少し皮肉なことに、Windowsはすでに数十年前にredistributable方式でこの問題を解決していた
Linuxにはよく知られているように安定した公開ドライバAPIが存在しないが、これこそがGoogleがFuschia OSを開発した動機だと聞いている。Linuxはユーザー空間とハードウェアの両方に対して、それぞれ異なる方向性を持っているわけだ
筆者はバージョンベースのAPIをあまり好んでいないようだが、私はアプリを作る段階からバージョン管理を必ず導入するよう常に勧めている。未来は予測できないのだから、いつか外的要因によって破壊的変更が自分にも起きるのは避けられない
実際のところ、筆者もバージョン管理を勧めていたのだと思う。本文では「バージョンはAPIを責任ある形で変更する方法」と書いているので、結局はバージョン管理自体を推奨していることになる。ただし、新バージョンへの移行は最後の手段にすべきだと言っている
私はエンドポイントにむやみに「v1」を付けるべきではないという意見に同意する。実際にAPIが成長する中で起きるのは、まず既存エンドポイントにフィールドやオプションを追加して後方互換性を保とうとすることだ。そして完全に互換性のない作業が必要になれば、たいていはエンドポイント名そのものを新しく付け直し、まったく新しいエンドポイント(/v2ではなく)を作る。もしAPI全体を変えなければならないなら、既存サービスを廃止して名前から付け直したまったく別のサービスを立ち上げることになる。25年間働いてきて、"/v1"と"/v2"が並行して使われるサービスは一度しか見たことがない
著者の意図は最初から/v1をエンドポイントに入れるなということではないと思う。要点は、新しいバージョン(/v2)が生まれないよう最善を尽くすべきだということだ。/v2ができると、バグ修正のたびに両方へコード変更を入れなければならず、条件分岐が指数的に増えてコードベースがスパゲッティのように複雑になる。結局、複数バージョン対応を招いた元の/v1設計は、将来互換性への配慮が足りなかったということだ
バージョン管理は後から追加しても問題ないと思う。たとえば最初は/api/postsで始めて、次のバージョンを/api/v2/postsとして追加すれば十分だ
最初からバージョンを埋め込む方式には賛成しない。そうすると本当に複数バージョンが頻繁に使われるようになり、それはむしろ望ましくないと思う
この記事はとても有益だった。ここに一つ助言を加えたい。APIドキュメントを入手する難しさとAPI品質は反比例する。もし契約書に署名しないとドキュメントが手に入らないなら、そのAPIの品質はひどいものだと考えてよい
idempotency keyをcommentテーブルに別途保存する代わりに、Redisのようなkey/valueストアに入れろと書かれていたが、すべての失敗ケースでこの方式が確実なidempotencyを保証できるのか気になる。たとえばサーバーが
SET key 1 NXのような条件付き書き込みを行い、すでにkeyが存在することを見つけたら、コメント作成はそのままスキップしなければならない。しかしこの時点で、先行リクエストが実際にはDBへ反映されていない可能性もある。idempotency keyの保存は実際の処理と同じトランザクション単位でコミットされ、必要ならロールバックもされるべきだ。結局、idempotency keyの本質は「この処理あるいはリクエストの一意なID」であるべきだ。たとえば「コメント作成」「コメント更新」など、それぞれに対応するリソース単位の識別子であるべきだということだカーソルベースのページネーションの利点は、ユーザーの観点では、ページを読み込んで「次へ」ボタンを押すまでの間に新しいアイテムが追加されても、すでに見た項目をもう一度見なくて済むことだ。カーソル方式では前ページの最後のオブジェクトIDを記録しておき、それ以降のアイテムを返すので、無限スクロールに特に向いている。一方で、カーソルベースには「Nページ目へジャンプ」機能を作りにくいという欠点がある
最近「API」と言うと、たいていはWebアプリにリクエストを送り、パラメータやヘッダーを設定してデータを取得するものを思い浮かべる。しかし本来APIとは「Application Programming Interface」、つまり「アプリケーションプログラムのインターフェース」という意味だ。1940年代に初めて使われ、その後1990年代まではほぼ別の意味なしに使われていた。APIの歴史は80年以上あり、非常に古い資料も数多くある。当時のプログラマーがどんな問題を扱い、どう解いたのかを考えてみると、今の自分にも役立つ部分があるはずだ
内部ユーザーを単に「ユーザー」と見なせばよいという意見には同意しない。たしかに彼らはより技術的で、プログラマーである可能性が高いが、それでも忙しく、自分のプロジェクトに集中していて、API変更に対応する時間や余裕が足りないことが多い。可能であれば、公開前にチーム内で十分に「dogfooding」(実運用)テストを行うことが重要だ。いったん外部公開したら、「userspaceを壊さない」という約束は必ず守らなければならない
内部ユーザーであれば、直接連絡して移行を促せるような計測ツールが通常は実装されている。そのおかげでAPIバージョンの廃止も可能になり、戦略的にバージョン管理を導入することには十分な魅力がある。実際にAPIバージョン管理に関わったことがあり、基本的にこれを使わない組織と比べても確かな効果があった
バージョン管理の方式はこの問題を解く助けになると思う。内部ユーザーに配慮する最善策の一つは、仕様について協業し、その仕様の作業中バージョンもステークホルダーに共有することだ。継続的に更新される文書であっても、基準点を設ければ内外のフィードバックも円滑になり、いたずらにポリシー上の衝突リスクさえ避ければ非常に有用に使える
idempotency keyをredisに保存するより、可能であれば実際のデータを書き込む同一トランザクション内でidempotency keyも一緒に保存するほうが確実だと思う
「userspaceを絶対に壊すな」という警告は本当に重要だ。Spotify、Reddit、Twitterなどが最近この原則を無視していて残念だった
参考までに、https://jcs.org/2023/07/12/api のリンクにはAPIに関する優れた推奨事項がよくまとまっているので、あわせて読むことを勧める