Memorandum.

備忘録をかねたアウトプットの場

「Web API: The Good Parts」読書メモ ~ その 3 ~

tl;dr

どうも私です。

前回に引き続き「Web API: The Good Parts」についてのアウトプットその 3 です。

3 章では「レスポンスデータの設計」について触れられています。

目次

Web API: The Good Parts

3 章 : レスポンスデータの設計

要点

データフォーマット

世の中的なデファクトスタンダードJSON である。

そのため、JSON にデフォルトとして対応し、
必要に応じて XML 等の別のフォーマットに対応する方針で良い。

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

別のフォーマットをサポートしたい、もしくはしなければならない場合に、
クライアント側にどのようにフォーマットを指定させるのか?

一般的には以下のような方法が使われる。

  • クエリパラメータを使う方法
  • 拡張子を使う方法
  • リクエストヘッダでメディアタイプを指定する方法

このうちどれを使用するのかは、かなり「好みの問題」である。

HTTP の仕様を最大限活用するならば、
リクエストヘッダで指定することになるが敷居は高い。

実際のサービスを調べると、クエリパラメータを使う方法が多い。

クエリパラメータを使う方法もしくは、 複数の方法に対応しておくことがおすすめとのこと。

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

API のアクセス回数がなるべく少なくなるように、
API それぞれのユースケースをきちんと考えることが重要である。

1 つの作業を完結するために複数回のアクセスが必要な API 設計は
「Chatty (おしゃべりな) API」と呼ばれ様々な弊害が生じる。

  • クライアント側の処理が煩雑になる
  • HTTP のオーバヘッドが上がり、レイテンシーが低下する
  • サーバの負荷上昇につながる

エンベロープは必要か

エンベロープとは日本語で「封筒」を意味する。
API の文脈では「すべてのデータを同じ構造でくるむこと」を意味する。

以下のように「header」と「body」をレスポンス構造に持ち、
実際のデータを「body」に、共通のメタデータを「header」に持っている。

{
  "header": {
    "status": "success",
    "errorCode": 0
  },
  "body": {
    "hoge": "xxx"
  }
}

こうしたすべての API が同じデータ構造を返すために、
実際のデータをくるむための構造をエンベロープと呼ぶ。

結論としては「エンベロープ」は冗長であり、やるべきではない。

Web API は基本的に HTTP がエンベロープの役割を果たしている。

HTTP にはヘッダがあり、そこに様々な情報を入れることができる。
エラー判定もステータスコードを利用することで実現可能である。

そのため、レスポンスヘッダの書き方を全 API で共通化した方が、
クライアント側の実装も抽象化できるため有益である。

データはフラットにすべきか

GoogleJSON Style Guide には以下のような記載がある。

「なるべくフラットにした方が良いが、階層構造があった方がわかりやすい場合もある」

住所を例に挙げているが、この場合は階層構造があった方がわかりやすいとのこと。

階層構造化する場合は慎重な判断が必要であり、
利便性による恣意的なグルーピングではなく、
意味的に有益なグルーピングを行った方が良い。

配列とフォーマット

配列をレスポンスとして返す場合に以下の 2 パターンが考えられる。

  1. 配列をそのまま返す
[
    {"id" : 1},
    {"id" : 2},
    {"id" : 3}
]
  1. オブジェクトで包む
[
    "animals" : [
        {"id" : 1},
        {"id" : 2},
        {"id" : 3}
    ]
]

どちらも JSON としては問題ないが、
以下の理由から筆者としては「2. オブジェクトで包む」がおすすめとのこと。

  • レスポンスデータが何を示しているかわかりやすい
    • オブジェクトのキーから何のデータかひと目でわかる
  • レスポンスデータをオブジェクトに統一することができる
    • トップレベルが配列やオブジェクトと API ごとの異なる場合...
      • クライアント側で共通処理がしづらい可能性がある
      • つまり、トップレベルをどうするか共通仕様を定めた方が良い
  • セキュリティ上のリスクを避けることができる ( 最も重要 )
    • トップレベルが配列の場合、JSON インジェクションに対するリスクが大きい
      • script 要素を利用して JSON データを不正に入手する手法
      • ※ 認証処理がある API はこの問題は関係ない

各データの名前

エンドポイントのデザインと同様に以下のポイントが重要である。

  • 多くの API で同じ意味に利用されている一般的な単語を用いる
  • なるべく少ない単語数で表現する
  • 複数の単語を連結する場合、その連結方法は API 全体を通して統一する (キャメルケース)
  • 変な省略形は極力利用しない
  • 単数系 / 複数形に気をつける

性別のデータをどう表すか

いくつかのサービスが「性別のデータ」をどのように表すかを確認した際に、
数値(e.g. 0 or 1)ではなく、文字列(e.g. male, female)で表すことが圧倒的に多い。

SSKDs 向けの API であれば「数値」も選択肢として考えられるが、 LSUDs 向けの API であれば「文字列」を選択した方がベータである。

日付のフォーマット

日付の表現方法には様々なものがある。

形式名
RFC 822 (RFC 1123 で修正) Sun, 06 Nov 1994 08:49:37 GMT
RFC 850 (RFC 1036 で廃止) Sunday, 06-Nov-94 08:49:37 GMT
RFC 3399 2015-10-12T11:30:22+09:00
Unix タイムスタンプ 1630503271

筆者の結論としては「RFC 3399」を採用するのがベターとのこと。

理由としては、これまでのフォーマットの問題を解決し、
使いやすいものを目指してインターネット上の標準形式として決められたものだからである。

タイムゾーンはサービスと使用者の性質によって統一して使用するものを選択する。

大きな整数と JSON

クライアント側によっては大きな整数を扱った際に問題がおこるため、 レスポンスデータとして大きな整数(e.g. bigint)を返す場合は注意が必要である。

  • クライアントが 64ビット整数を 32ビットで処理している場合
    • 桁溢れが発生してしまう可能性がある
  • クライアントが大きな整数を正しく扱えない言語を使用している場合
    • e.g. Javascript : 誤差が発生してしまう

回避策として「 こうした数値を文字列で返す 」という方法がある。
Twitter は id の他に id_str と型を文字列にしたものを返している。

エラーの表現

なるべく多くの情報をクライアントに返し、
クライアントが問題を解決して API を利用できるようにした方がベターである。

そうしなければ「使いづらい API」という烙印を押されてしまう。
また、間違ったアクセスが増えサーバの負荷を増やす要因になる可能性もある。

そのため、以下の 2 点に気をつける必要がある。

  1. ステータスコードでエラーを表現する
  2. エラーの詳細をクライアントに返す
1. ステータスコードでエラーを表現する

適切な HTTP ステータスコードを使用することが重要である。

ステータスコード 意味
1xx 系 情報
2xx 系 成功
3xx 系 リダイレクト
4xx 系 クライアントサイドに起因するエラー
5xx 系 サーバサイドに起因するエラー

世の中の API によってはエラー時にも「2xx系」を返している場合もあるが、
これは使い方として正しくなく、また問題も引き起こす可能性がある。

汎用的な HTTP のクライアントライブラリには、
ステータスコードを見て処理を分岐しているものが多く、
こうした用意された処理をそのまま利用できなくなってしまう。

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

ステータスコードだけでは不十分であり、エラーの詳細を返すことが重要である。

エラーの詳細を返す方法は以下の 2 パターン考えられる。

  1. HTTP のレスポンスヘッダに独自定義したヘッダ項目を用意する
  2. レスポンスボディにエラー用のデータ構造を用意する

多くの API はクライアント側の利便性から「2」を採用していることが多い。
そのため、特に理由がないのであれば「2」の方法を採用して問題ない。

エラーの詳細としては、以下のような情報組み合わせて返した方がベターである。

  • エラーメッセージ
    • e.g. {"message" : "Bad Authentication token"}
    • 非開発者向け(エンドユーザ)と開発者向けでわけるという方法もある
  • エラーの詳細コード
    • e.g. {"code" : 1000}
    • HTTP ステータスコードのようにルール化し管理しやすくすると便利である
  • 詳細情報へのリンク

感想

個人的には「API 全体としての統一感」や「省略形を使用しない」という点は強く同意でした。

同じサービスの API であるのにもかかわらず、
統一感のないレスポンス構造やフィールド名を採用している場合、
クライアントはもちろん API 開発者も開発スピードが減少するため、
お互いに良いことなし、、だと思うんですよね。。

また、エラーの表現についても同様だと思います。

クライアントがエラーの原因を解決できるようにするだけでなく、
API 開発者が問い合わせを受けた際に、つまり運用の際に、
何が起きたのかヒアリングしやすい仕組みも「エラー表現」に繋がるのだと個人的には感じました。