これは何?
今チームで使っているAPIガイドラインを公開してみます。
RESTful APIの設計のための資料をあさりつつ、アプリのクライアント向け(特にAndroid)やRuby (GrapeとRails)向けに調整したり決定したりした部分があり、その辺りのプラクティスが含まれています。
このガイドラインでは主にRESTful APIの一般的なプラクティスでは不足している内容について記述されています。
※iOSはObjective-C製で、RestKitやMantleを使わずAFNetworking/NSURLSessionを使っていますが、AndroidはRetrofitを使っています。
(特にiOS用に)こうしたほうが良い!ということがあればコメントお待ちしております!
副題:(チーム名)のAPIを支える技術
関連資料
書籍など
-
Web API: The Good Parts
- 書籍、網羅的、日本語版あり
-
Web API Design ebook by apigee
- ページ数が少なくてシンプルなので最初によむと便利
-
http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
- 日本語訳「WebAPI 設計のベストプラクティス」 http://qiita.com/mserizawa/items/b833e407d89abd21ee72
- 困ったときに使ったサイト
-
http://qiita.com/hnakagawa/items/9107b46af907e9647046
- アプリ用に設計するときに考慮しておいたほうが良いこと
他のAPIを参考にする
「APIには知見が詰まっているから参考にしよう」とapigee本に書かれています。
- Facebook: https://developers.facebook.com/docs/graph-api
- entityの定義がしっかりしている
- たまに買収した会社のルールになっていて統一されていない箇所があるらしい
- Github: https://developer.github.com/v3/
- シンプルだし、エラーの返し方がわかりやすい
- Rails基準なので、JSONのkeyがsnake_caseになっている
- FourSquare: https://developer.foursquare.com/docs/
- ドキュメントが見やすい印象
- HTTPの動詞ではなく、動詞が含まれたURLを使っている
- Twitter: https://dev.twitter.com/rest/public
- 正直ごちゃっとしてるかも
指針
- 可能な限りスタンダード(RESTfulや命名の慣習など)に従うこと。
- APIはアプリを作るためのものなので、シンプルかつ明快に保つ。
- それによりクライアント側に無理をさせないようにし、エンティティ変換のような処理はAPI実装側に寄せる。
URL
- RESTfulなURLの一般的なプラクティスに準拠すること(関連資料)
- リソースは複数形で表現
- 動詞は避ける
- 例外的に、RESTfulで表現できない操作は、リソースに対する動詞(例:/users/123/change_email)として定義する
- 単語の区切りがある場合はアンダースコアを使用する
- ○ POST /media_files
- ✕ POST /mediafiles
- ✕ POST /media-files
- ✕ POST /mediaFiles
- → URLの区切り方は諸説ある(ハイフン、アンダースコア、camelCase)が、コードで表現した際にハイフンはマイナスになってしまうことと、camelCaseはURLとして一般的ではないことを踏まえての選択です(あとrailsの標準というのも・・
リクエスト
- エンティティを送信する場合は主としてJSONを使用する
- JSONが利用できない場合(ファイルアップロードなど)はクエリパラメータを用いる
- 検索条件などを指定する(パラメータ)場合はクエリパラメータを用いる
- セッショントークンなどはヘッダを用いる
クエリパラメータ
キーおよびenum値はsnake_caseで記載する
?key_foobar=value_hogefuga&key2=value2&...
→ Rubyの変数名がsnake_caseなこと、URL的にcamelCaseよりもsnake_caseが一般的に見受けられること、パスの部分はsnake_caseに統一していることを踏まえての選択です
ヘッダ
X-Foobar-Hogefuga形式を使用すること
→ HTTPのリクエストヘッダが "Content-Type" 形式の表現であるためです
レスポンス
エンティティの返却にJSONを用いること。
※ヘッダでの情報の返却は、現在のところretrofitでの扱いが煩雑なため推奨されない。
POST、PUT、PATCHでは、変更されたリソースのエンティティを単品で返却する。画面の再描画やデバッグに役立つ。
また、リソースが作成された時は201 Createdを返却すること。
http://tools.ietf.org/html/rfc2616#section-9.5
リクエスト・レスポンスJSON
エンティティの再利用
アプリケーション内で扱う情報(User、Comment、etc...)について、単一のエンティティ構造を定義し、様々なAPIでなるべく共通の構造を使いまわす。
例えばUserのエンティティを定義したら、User情報の変更にも取得にも同じ構造を使用する。
このことで、クライアント側の処理を共通化しやすくなる。
optionalなキーが存在してもよい。リクエスト時にはpasswordが入っているが、レスポンス時には入っていないなど。この場合、レスポンスJSON側でキーが非存在であるべき。
エンティティのキー
エンティティ名がキーに含まれると冗長なので避ける。すなわち、エンティティのidはuser_idではなくidと表現する。
→ これにより、エンティティの入れ子があっても自然な形で参照できる(user.id vs user.user_id)
キーはcamelCase
JSONのキーは全てlowerCamelCaseとする。Ruby側ではsnake_caseを用いて記述し、それをFormatter/Parserを用いてcamelCaseと相互変換する。
lowerCamelCaseにしておくことで、JavaScriptの規約にも、JavaのGSONの変換にもiOSのproperty名にも簡単に適合させられる。
値の表現方法
- 時刻: ISO 8601
- ミリ秒はoptional、互換性の都合でレスポンスは秒単位にしておいたほうがよさそう)
- timezoneを内包できたり、年月日などの形式も透過的に扱えるためISOを使う
- enum値: snake_case 小文字のみ
- Javaのenumでの扱い、RubyのsymbolやURLの命名規則を総合的に判断した結果
- 課題として、enumで指定した値("enum_key")がレスポンスのキー("enumKey": ...)になる場合に統一性が損なわれる
- ID/UUID
- サーバとクライアントで同じIDを使用すべき箇所、同期や自律的な更新(ボタン押したらクライアント側でエンティティ書き換えながらリクエスト投げる、など)をする場所はUUIDを使う
- UUIDはRFCに従い、小文字で表現しつつ、大文字が入ってきても小文字に変換するようにすること
- その他の値: 値にあった型を使用する
- 例えば、booleanは1/0ではなくtrue/falseで返す
{
"id" : 123,
"relationName" : "grand_father",
"createdAt" : "2004-04-01T12:00:00+09:00"
}
完了状態の返却
エンティティを返却できない場合は、下記のようなレスポンスを返す。
{
"status" : "success"
}
特に202 Acceptedなどを返す場合は、statusに何をやったか含めるのが良い。
エラー返却
- 適切なレスポンスコードを使用すること
- reasonに機械判読可能なエラー内容の文字列を含めること。
- TODO: codeにreasonに対応する数値のエラーコードを含めること。
- その際、ステータスコードの先頭1桁目(注: RFCにて、不明なコードでも1桁目は最低限確認すべきとされている)+3桁のコードという形で割り当てなくてはならない(must)。
- 例: 4001〜4999、5001〜5999
- → Objective-Cではenumが数値で表現されることへの配慮です
- userMessageにユーザに表示するためのエラーメッセージを含めなくてはならない(must)。
- アプリはこのエラーメッセージをユーザに直接表示してよい(shouldではない)。
- TODO: クライアントでエラーの判別をするときはreason/codeだけを見るようにすることが望ましい(should)
- なお、2015/07現在、この仕様に準拠していないAPIが多くあるが、徐々に移行することが望ましい
{
"reason" : "invalid_credential",
"code" : 4123,
"userMessage" : "メールアドレスかパスワードが間違っています。"
}
レスポンスコードとreasonの定義
(社内用)
バリデーション
- 原則、バリデーションの実装はクライアント側で行う(UX上の都合と、APIをシンプルに保ち更新しやすくするため)。
- APIの返却値にはバリデーションエラーの詳細を含める(デバッグのため)。(原則、アプリ側でもバリデーションを行っているので、機械的に処理できない表現でも良い)
- (アプリ側)ユーザ向けの仕様としてのバリデーションが不要な箇所はバリデーションをつけない
- (API側)システムの気密性、一貫性を維持することが目的ではない(ユーザ向けの仕様としての)バリデーションはせず、アプリ側に任せる
- 実装が二度手間になるため
- アプリのバージョンごとにAPIでのバリデーション内容を管理しなくてはいけなくなるため
- 例:パスワードの文字数はサーバ側にも実装すべきだが、表示の都合でニックネームを10文字程度に絞りたいとかはクライアントだけでも良い
適切なレスポンス・レスポンスコードを使う
RESTfulの慣習に従えばよいが、今そうなってない箇所があるので書いておきます。
バリデーションエラーが400なのは現状は仕様ということになってます・・orz
※注:バリデーションエラーは422として表現するのが望ましい (Githubの例)
- POST時の200には、操作したエンティティの更新結果を含める
- リソースに対する操作ではない場合(reset_passwordなど)は
{"status":"..."}
を返して良い
- リソースに対する操作ではない場合(reset_passwordなど)は
- DELETE時は204 No Contentを使用する
バージョニング
- URLによるAPI全体のバージョニング(/api/v1/...)よりも、API endpointごとに必要な箇所でバージョニングする
- エンティティのキーを増やす場合はバージョニングは不要(未使用のキーの増加は問題ない)
- エンティティのキーをリネーム・削除する場合は、(何らかの方法で)バージョニングする
- バージョニング方法は未定(ヘッダを推奨する)
参考: 「APIのバージョニングは限局分岐でやるのが良い」 http://kenn.hatenablog.com/entry/2014/03/06/105249
通信障害耐性
モバイルのクライアントの場合、リクエストが成功しレスポンスが送信される時点で通信エラーが発生する可能性も考慮する必要がある。
- ユーザ登録処理や投稿処理など、二重に実行されると不具合とみなされる現象が発生する場合は、ID値にクライアント側で生成したUUIDを使用する。
- 同じUUIDで2回作成リクエストが実行された場合は、409 Conflictまたはそれに該当するエラーレスポンスを返却する。
- サーバからのレスポンスによってクライアントに状態を保存する場合、必ずリトライの機会を設ける。そうしないとサーバ側とクライアント側で状態の不一致が恒久的なものになる。