🐍

精読「Web API: The Good Parts」(2)

2025/01/23に公開


Web API: The Good Parts
Web APIの設計と開発における「本当に大切な部分」を凝縮した一冊。シンプルで使いやすいAPIを実現するためのベストプラクティスと、その背景にある思想を解説します。RESTの基本から、セキュリティ、パフォーマンス、エラーハンドリング、バージョニングまで、実務に役立つ知識が詰まっています。

この書籍では、実例を通じて良いAPI設計の判断基準を学べるだけでなく、なぜそのアプローチが優れているのかを深く理解できます。さらに、「これだけは避けたい」という悪い例も取り上げられており、実践的なスキルが身につきます。

開発者、アーキテクト、プロダクトマネージャーにとって、Web API設計の基礎と応用を学ぶ最良のガイドとなる一冊です。

関連記事

レスポンスデータの設計

Web APIのレスポンスデータ設計について解説する。Web APIはプログラムで処理しやすいデータを返す仕組みであり、レスポンスデータもその点を意識して設計する必要がある。ここでは「美しいレスポンスデータ」をどう作るかを考える内容となっている。

データフォーマット

JSONが主流

Web APIではJSONが基本。軽量で扱いやすいから使われる。XMLは表現力高いけど、今はあんまり必要とされてない。

データフォーマットの指定方法

クライアントがフォーマットを指定する方法は以下の3つ:

  1. クエリパラメータ(例: ?format=json
  2. 拡張子(例: /users.json
  3. リクエストヘッダ(例: Accept: application/json

現実的にはクエリパラメータが一番使いやすい。リクエストヘッダはHTTP仕様には忠実だが、採用例は少ない。

オススメの方法

1つならクエリパラメータ。複数対応するならクエリパラメータとリクエストヘッダの両方をサポートすると良い。

JSONPの取り扱い

JSONP (JSON with Padding) は、異なるドメインからデータを取得するための手法。JSONを関数でラップしてスクリプトとして読み込み、同一生成元ポリシーを回避します。

特徴

  • 利点: 他ドメインからデータを取得可能。
  • 制約: GETリクエストのみ、HTTPヘッダ設定ができない。

実装方法

  • クライアントは <script> タグでJSONPを読み込み、コールバック関数で処理。
  • エラー処理が難しく、サービス側はエラー情報をJSON内に格納することが一般的。

セキュリティ

セキュリティリスクがあるため、CORSなどの代替方法が推奨される。

データの内部構造の考え方

APIのレスポンスデータを設計する際、アクセス回数を減らすことを優先すべき。例えば、友達リストを取得するAPIで、単にIDを返すだけだと、クライアント側でIDを使ってさらにユーザー情報を取得するために複数回APIを叩く必要が生じる。この場合、IDだけでなく、ユーザー名やプロフィール情報などの詳細を一度に返すようにすることで、APIの利用効率が向上する。

Chatty API(複数回アクセスが必要な設計)は避け、利用しやすいAPIを目指そう。

レスポンスの内容をユーザーが選べるようにする

利用者に必要な情報だけを返す方法として、クエリパラメータで選択できる仕組みがある。例えば、ユーザー情報の一部(名前や年齢)だけを取得したい場合、クエリパラメータを使用して指定できる。

エンベロープは必要か

APIレスポンスのデータを「エンベロープ」で囲むことは冗長であり、HTTPヘッダーの機能を活用する方が効率的。エラーステータスなどのメタデータはHTTPのレスポンスヘッダーに含め、実際のデータはボディに返すことで、無駄を省き、よりシンプルで効率的なAPI設計を実現できる。

各データのフォーマット

各データの名前

APIのデータ項目名は、以下の原則を守るべき

  1. 一般的な単語を使用: よく使われる単語を使うことで誤解を避け、意味が共有されやすくなる。
  2. 単語数を少なく: 意味を正確に伝えながら、なるべく短い名前を使用することを目指す。例:userRegistrationDateTimeよりcreatedAtが好ましい。
  3. 連結方法の統一: 複数単語をつなげる方法(キャメルケース、スネークケースなど)はAPI全体で統一する。一般的にはJSONではキャメルケースが推奨される。
  4. 省略形を避ける: 略語は誤解を招く可能性があるため、使わない方が良い。例えば、timelinetlにするなどは避ける。
  5. 単数形/複数形の使い分け: データが複数形の配列であれば、フィールド名も複数形を使う(例:friends)。

性別データの表現方法

性別情報は以下の2種類で表現することが一般的

  1. 文字列: malefemaleといった文字列形式が多い(例:Facebook、Google+、Genderize.ioなど)。
  2. 数値: 1や2などの数値で表現されることもあり(例:楽天、Easypromos)。

また、性別(sex)と社会的性別(gender)の違いにも注意が必要。sexは生物学的性別を指し、genderは社会的・文化的性別を指す。Facebookのように、性別を多様に扱うサービスでは、文字列で多様な値を使うケースが増えている(例:Agender、Transgender、Non-binaryなど)。将来的に性別の選択肢を増やす可能性も考慮し、genderフィールドを文字列形式で設定することが推奨される。

レスポンスデータの設計

レスポンスデータの設計についての基本的なポイントを理解するために、実際にサービスのデータをどのような構造で返すかを考える際のいくつかの重要な視点が挙げられる。ここではいくつかの要点を押さえる。

  1. DB構造とAPIレスポンスの非対応:

    • APIのレスポンスデータは、内部で使用しているデータベース(DB)のテーブル構造そのままである必要はない。DBのテーブルは効率的なデータ格納やクエリのために設計されており、APIレスポンスはユーザーやクライアントが簡単に扱えるような形にすることが重要。
  2. クライアント側での利便性:

    • 例えば、SNSの友達一覧を返す場合、ユーザーIDだけではクライアント側でIDを基に個別にデータを問い合わせなければならず、効率的ではない。代わりに、ユーザーIDに加えて、必要なユーザー情報(名前やプロフィール画像など)を一度に返すことで、クライアント側が追加のリクエストを発生させる必要がなくなる。
  3. データ構造の一貫性:

    • 返すデータ構造はできるだけ一貫性があり、シンプルであるべき。例えば、ユーザー検索APIと友達一覧APIで同じようなデータ構造(名前、プロフィール画像、IDなど)を使うことで、クライアント側でデータを扱う際の負担が軽減される。
  4. ユースケースに基づいた設計:

    • 単純化しすぎて必要な情報が欠けてしまうことがないように、APIのユースケースに基づいて、クライアントが最も簡単に処理できる設計を行うべき。例えば、APIのレスポンスデータは「必要最低限の情報」でありながら、クライアント側での使い勝手を考慮して設計することが重要。
  5. わかりやすさとシンプルさ:

    • データ構造はわかりやすく、できるだけシンプルにすることが求められるが、そのシンプルさは目的に合わせて調整する必要がある。例えば、FacebookのAds APIでは、「Objects」として広告関連のオブジェクト構造が定義されており、APIで利用されるデータ構造がわかりやすく文書化されている。こういった事例を参考に、クライアント側での処理を容易にするデザインを心がけると良い。

これらのポイントを踏まえたレスポンスデータの設計を行うことで、クライアント側での処理の負担を減らし、よりスムーズな開発とユーザー体験の向上が期待できる。

エラーの表現

エラーを表現する方法について、重要なポイントは以下の通り。

ステータスコードでエラーを表現

  • エラーが発生した際、適切なHTTPステータスコード(例:404 Not Found、500 Internal Server Error)を返すことが大切。
  • 400番台(クライアントエラー)や500番台(サーバエラー)など、コードのカテゴリに沿ったものを選ぶ必要がある。
  • エラー情報を返す際に、常にステータスコードを正しく使用し、200番台(成功)のステータスコードをエラー時に返さないことが重要。

エラーの詳細をクライアントに返す

  • ステータスコードだけでは不十分で、詳細なエラー情報をレスポンスボディやヘッダで返すべき。
  • レスポンスボディにエラーコードやメッセージ、関連する情報をJSONフォーマットで返すことが一般的。
    • 例:{ "error": { "code": 2013, "message": "Bad authentication token", "info": "http://docs.example.com/api/v1/authentication" } }

エラー詳細情報に含めるべき内容

  • エラーコード、メッセージ、そして詳細情報へのリンクを含めると良い。
  • エラーメッセージは開発者向けとユーザー向けの2種類を用意し、どちらも明確に伝わるようにすることが望ましい。

エラー時にHTMLが返ることを防ぐ

  • エラーが発生した場合、APIは通常JSONやXMLなどの形式でエラー情報を返すべき。HTMLで返すと、クライアントが正しく処理できない可能性がある。
  • Acceptヘッダを使ってフォーマットを指定することで、クライアントは期待通りのレスポンスを受け取れるようにする。

これらを考慮することで、エラーが発生した際にもユーザーが問題を理解し、迅速に対応できるようになる。

まとめ

  • [Good] JSON、あるいは目的に応じたデータ形式を採用する
  • [Good] データを不要なエンベロープで包まない
  • [Good] レスポンスをできるかぎりフラットな構造にする
  • [Good] 各データの名前が簡潔で理解しやすく、適切な単数複数が用いられている
  • [Good] エラーの形式を統一し、クライアント側でエラー詳細を機械的に理解可能にする

HTTPの仕様を最大限利用する

API仕様を決定する際は、既存の標準仕様やデファクトスタンダードを遵守し、HTTPの仕様を理解して活用することが重要。これにより、使いやすいAPIが実現できる。

HTTPの仕様を利用する意義

HTTP仕様を利用する意義は、RFC(Request for Comments)で定義された標準仕様を理解し、API設計に活用することで、独自仕様を避け、バグの発生を減らし、広く再利用される可能性を高めることにある。HTTPのリクエストとレスポンスはヘッダとボディから成り、ヘッダにはメタ情報を含めることができる。このため、レスポンスデータを独自のエンベロープで包む必要はなく、HTTPの仕様をそのまま活用することが推奨される。

ステータスコードを正しく使う

ステータスコードは、APIやウェブアプリケーションにおけるレスポンスの結果を示す重要な部分。以下のポイントを押さえておくと良い。

ステータスコードの概要

  • 100番台: 情報的なレスポンス(例: リクエストの処理状況の通知)
  • 200番台: 成功(リクエストが正しく処理された)
  • 300番台: リダイレクト(他のリソースを参照する必要がある)
  • 400番台: クライアントエラー(リクエストに誤りがある)
  • 500番台: サーバエラー(サーバ側で問題が発生した)

200番台の詳細

  • 200 OK: リクエストが成功した場合に最もよく使われる。
  • 201 Created: 新しいリソースが作成された場合に使用(例えば、ユーザーの作成など)。
  • 202 Accepted: リクエストは受け付けたが処理が完了していない場合に使用(非同期処理など)。
  • 204 No Content: レスポンスが空(例えば、DELETEメソッドで削除が完了した場合など)。

300番台の詳細

  • 301 Moved Permanently: リソースが恒久的に移動した場合に使用(URLが変更された場合など)。
  • 302 Found: リソースが一時的に移動している場合に使用。
  • 303 See Other: リソースの新しい場所を指示する場合に使用(GETメソッドへの変更が一般的)。
  • 307 Temporary Redirect: 一時的なリダイレクトで、元のHTTPメソッドをそのまま使用する。

不適切な使用を避ける

ステータスコードはクライアントがエラーを認識しやすくするためにも正しく使用するべき。例えば、エラーが発生したのに200 OKを返すのは混乱を招く可能性があり、クライアント側の処理が誤動作する原因となる。HTTPステータスコードは、クライアント側でエラーや成功の処理を分岐させるための重要な情報。

まとめ

  • 正しいステータスコードを返すことで、クライアントは適切にレスポンスを処理できる。
  • 例えば、リソース作成時は201、削除時は204など、HTTPの慣習に従ったステータスコードを使用する。
  • クライアント側の問題を避けるために、APIの設計時にステータスコードの利用規則を明確に定義しておくことが重要。

キャッシュとHTTPの仕様

もちろん、もう少し詳細に説明しますね。

4.3 キャッシュとHTTPの仕様

キャッシュは、クライアント側で一度取得した情報を保存しておき、再度その情報が必要になった際に再取得を避けて使う仕組み。これにより、通信量やレスポンス時間を削減でき、システムの負荷を軽減することが可能。

キャッシュのメリット

キャッシュは、以下のようなメリットをもたらす。

  1. ユーザー体験の向上: 通常、サーバへのリクエストを減らすことで、クライアント側でのレスポンス速度が速くなる。これにより、ページやデータの表示が速くなり、ユーザーの満足度が向上する。

  2. オフラインでの利用: ネットワーク接続が不安定または切れている場合でも、キャッシュがあれば保存したデータを再利用することができる。これにより、ユーザーはネットワーク接続がなくてもある程度サービスを利用し続けられる。

  3. 通信コストの削減: サーバへのリクエストや転送量が減ることで、ユーザーの通信量が減り、サービス提供者側のサーバの負担も減少する。これにより、通信コストやインフラ費用が削減される。

  4. サーバ負荷の軽減: キャッシュによってサーバへのアクセス回数が減るため、サーバ側のリソース(CPUやメモリ、帯域幅)の消費が減る。これにより、より効率的なリソース利用が可能となり、コスト削減が実現する。

プロキシサーバの影響

キャッシュの実装には、クライアントとサーバの間に位置するプロキシサーバも関連する。プロキシサーバは、リクエストを中継する際にレスポンスデータをキャッシュすることがある。このため、プロキシサーバがデータをキャッシュしている場合、クライアント側が再度サーバへリクエストを送っても、プロキシサーバからキャッシュされたデータが返されることがあるため、意図しない結果になることがある。

例えば、プロキシサーバがキャッシュのデータを期限内と認識した場合でも、クライアントがキャッシュを利用していないと思ってリクエストを送ると、サーバからのデータ取得がスキップされることが起こる。これが原因で古いデータがクライアントに送られる可能性がある。このような問題を防ぐためには、サーバ側でのキャッシュ管理や適切なキャッシュ制御ヘッダを設定することが重要。

Expiration Model(期限切れモデル)

期限切れモデルは、キャッシュのデータに「有効期限」を設け、その期間内にアクセスがあればキャッシュを再利用し、期限が過ぎた場合には再度サーバにアクセスして最新のデータを取得する仕組み。

  • Cache-Controlヘッダ: これは、レスポンスに対してキャッシュの制御を行うためのヘッダ。max-ageを指定すると、レスポンスがキャッシュされてからの経過時間で有効期限が決まる。例えば、Cache-Control: max-age=3600は、レスポンスを受け取ってから1時間(3600秒)までキャッシュを保持するという意味。

  • Expiresヘッダ: このヘッダは、キャッシュが有効である期限を絶対時間で指定する。たとえば、Expires: Fri, 01 Jan 2026 00:00:00 GMTは指定された日時までキャッシュを有効にすることを意味する。この形式はRFC 1123に従う必要がある。

Validation Model(検証モデル)

検証モデルは、キャッシュされたデータが最新かどうかをサーバに問い合わせる方法。キャッシュが「新鮮」であれば、サーバへのリクエストは行われず、キャッシュのままデータを使用するが、もしデータが「古い」と判断されれば、サーバが最新のデータを返すという流れ。

  • 条件付きリクエスト: クライアントがサーバにリクエストを送る際、条件付きリクエストを使用して「もしデータが更新されていれば新しいデータを送ってください」とサーバに伝えることができる。サーバ側では、データが変更されていない場合、304 Not Modifiedというレスポンスを返す。この場合、サーバから新しいデータは送られず、クライアント側のキャッシュがそのまま使用される。

    • If-Modified-Sinceヘッダ: このヘッダを使うことで、リクエスト時に指定した日時以降に変更があったかをサーバに確認する。もし変更がなければ、304レスポンスが返される。

    • If-None-Matchヘッダ: エンティティタグ(ETag)というキャッシュのバージョンを指定して、リクエストを行う方法。サーバはこのETagを使用して、データが変更されていない場合には304レスポンスを返す。

このモデルは特に、大きなデータを扱うAPIにおいて有効。データが変更されていない場合、再度同じデータをダウンロードすることなく、少ない転送量でキャッシュされたデータを使い回せるため、通信量の節約になる。

キャッシュ制御の重要性

HTTPでキャッシュを活用するためには、Cache-ControlExpiresの設定が重要。これらを適切に設定しないと、意図しないデータがキャッシュされ、最新の情報がユーザーに届かないといった問題が発生する。

キャッシュ制御ヘッダを適切に管理し、検証モデルと期限切れモデルの適切な使い分けを行うことで、より効率的なシステム運用が可能になる。また、プロキシサーバを意識したキャッシュ管理も行い、データの整合性を保つことが大切。

メディアタイプの指定

メディアタイプの指定に関する詳細な説明がされている。ここでは、HTTPリクエストやレスポンスで送信されるデータ本体の形式を示す「メディアタイプ」の役割について述べている。

  • メディアタイプとは?
    メディアタイプは、データの形式(例えばJSON、XML、画像ファイルなど)を表す。これは、HTTP通信で交換されるデータがどのような形式かを指定するために利用される。リクエスト側は自分が処理可能なメディアタイプを指定し、レスポンス側はデータの形式を「Content-Type」ヘッダで指定する。

  • メディアタイプの指定方法
    メディアタイプは、トップレベルタイプ(例えばapplicationtext)とサブタイプ(例えばjsonhtml)で構成され、オプションで追加のパラメータを含めることができる。例として、Content-Type: application/jsonは、JSON形式のデータを表す。

  • 代表的なメディアタイプ
    いくつかの代表的なメディアタイプが挙げられている。例えば:

    • text/plain: プレーンテキスト
    • text/html: HTML
    • application/json: JSON
    • image/png: PNG画像
    • video/mp4: MP4動画
    • application/zip: ZIPファイル
      などが例として紹介されている。
  • Content-Typeの指定の重要性
    正しいメディアタイプを指定することが非常に重要。なぜなら、クライアントがそのContent-Typeを基にデータを処理するため。誤ったメディアタイプが指定されると、クライアントがデータを正しく処理できない場合がある。

    例えば、iOSのネットワークライブラリAFNetworkingは、Content-Typeapplication/jsonであることを期待しており、異なるメディアタイプが指定されるとエラーが発生することがある。もしJSONデータがtext/htmlで送られた場合、クライアントが正しく解析できず、エラーが発生する可能性がある。

  • x-で始まるメディアタイプ
    IANAに登録されていないメディアタイプは、x-で始まるサブタイプを使用することがある。これらは、主に新しいフォーマットやあまり広く使用されていないものに使われる。例えば、application/x-msgpack(MessagePack)やapplication/x-yaml(YAML)などがこれに該当する。

  • 自分でメディアタイプを定義する場合
    新たにメディアタイプを定義する場合、x-で始まるものは避けるべき。代わりに、IANAに登録されていない場合でも、新たに定義されたサブタイプにvnd.(ベンダー接頭辞)などの適切な接頭辞を付けて登録するべき。

このように、HTTPのメディアタイプの正しい指定は、APIの利用やデータのやり取りにおいて非常に重要。

同一生成元ポリシーとクロスオリジンリソース共有

同一生成元ポリシー(SOP)は、異なるオリジン(ドメイン、プロトコル、ポートが異なる)間でのリソースの読み込みを制限するセキュリティポリシー。これにより、例えばhttp://www.example.com/http://api.example.com/は異なる生成元として扱われ、XHRで直接データのやり取りができない。これに対し、**クロスオリジンリソース共有(CORS)**は、特定のオリジンからのアクセスのみを許可する方法で、セキュリティを保ちながら異なるオリジン間でのリソース共有を可能にする。

CORSでは、クライアントがOriginヘッダをサーバに送信し、サーバがそのリクエストを許可するかどうかを決定する。許可する場合、サーバはAccess-Control-Allow-Originヘッダで許可されたオリジンを返す。

また、プリフライトリクエスト(OPTIONSメソッド)は、特定の条件(HTTPメソッドやヘッダが標準的でない場合)で、実際のリクエストが送信される前に行われます。サーバはそのリクエストを受け入れられるかを確認するために、必要なヘッダ(例:Access-Control-Allow-Methods)を返す。

CORSはセキュリティ上JSONPよりも安全であり、ほとんどのモダンブラウザが対応しているが、古いブラウザ(特にIE8/9)はCORSの全機能に対応していない。


【初心者向け】プロキシ と CORS についてまとめてみました。(図解)より

独自のHTTPヘッダを定義する

独自のHTTPヘッダを定義する方法について。HTTPヘッダは、メタデータを送信するために使用され、標準のヘッダだけではカバーできない情報を送信するためにカスタムヘッダを作成する必要がある場合がある。

独自のヘッダを定義する際の一般的な方法として、ヘッダ名の先頭に「X-」を付けることが挙げられる。例えば、「X-AppName-PixelRatio: 2.0」といった形式。この接頭辞「X-」は、過去に一般的に使われてきたが、近年では「X-」を付けない方が推奨される場合もあり、RFC 6648ではその使用を避けるべきだとされている。

さらに、HTTPヘッダの名前を定義する際は、IANA(Internet Assigned Numbers Authority)のガイドラインに従って、ASCII文字(スペースや記号を除く)で構成された「トークン」として命名する必要がある。ヘッダ名のスタイルとしては、パスカルケース(単語の最初を大文字)やハイフンで区切った形式が一般的。

また、X-を付けるべきかどうかについては議論があり、最も重要なのは統一性を保つこと。もし「X-」を付けたヘッダを使用しているのであれば、今後定義するヘッダも同じ形式にするのがベスト。

このように、独自のHTTPヘッダを定義する際には、慣習やRFCガイドラインに従い、サービス名や目的に応じた適切な命名を行うことが重要。

まとめ

  • [Good] HTTPの仕様を最大限利用し、独自仕様の利用を最低限にとどめる
  • [Good] 適切なステータスコードを用いる
  • [Good] 適切な、なるべく一般的なメディアタイプを返す
  • [Good] クライアントが適切なキャッシュを行えるように情報を返す

参考

Discussion