google
WebAPI
api
googleapi
GoogleAppsAPI

GoogleのWebAPI設計とWebAPI設計のベストプラクティスを比較してみる

More than 1 year has passed since last update.

追記
GoogleがAPIのデザインガイドを作ったのでリンクを記載しておきます。
https://cloud.google.com/apis/design/


翻訳: WebAPI 設計のベストプラクティスというWebAPIに関する記事の内容と筆者が普段使っているGoogleのWebAPIはどうなっているかを比較してみました。
GoogleのWebAPIと言ってもたくさんあるため本記事ではCalendarAPIの予定情報をベースに比較していきます。
こんな感じなのかという雰囲気だけでも掴んでもらえれば幸いです。

TL;DR

WebAPI設計のベストプラクティスを満たす項目には○を記載

項目 GoogleWebAPI 補足
RESTful な URL にしよう
いつ何時も、SSL 通信を使おう
かっこいい仕様書を作ろう
バージョンは URL に含めよう
フィルタ・ソート・検索はリクエストパラメータでやろう
レスポンスのフィールドを絞れるようにしよう
作成・更新の後は変更後の情報をフルで返そう
HATEOAS を採用するのは待とう
可能な限り JSON で返そう
フィールドの命名規則を考えよう キャメルケース
JSON はデフォルトで整形しよう
要素はラップせずに返そう
追加・更新時のリクエストボディには JSON を使おう
ページング情報はレスポンスヘッダに入れよう × レスポンスボディ内のpageTokenを使用
関連データを埋め込む手段を作ろう × Google内のデータの持ち方的に難しいと思われる
HTTP メソッドを上書きしよう
リクエスト制限情報はレスポンスヘッダに入れよう Google Developer Consoleを使用
トークン認証を使おう
キャッシュの情報をレスポンスヘッダに入れよう
ちゃんとしたエラーメッセージを返そう
HTTP ステータスコードを有効活用しよう

RESTful な URL にしよう

元記事

GET /tickets - チケットのリストを取得する
GET /tickets/12 - 指定したチケットの情報を取得する
POST /tickets - 新しいチケットを作成する
PUT /tickets/12 - チケット #12 を更新する
PATCH /tickets/12 - チケット #12 を部分的に更新する
DELETE /tickets/12 - チケット #12 を削除する

Google
GET /events - 予定のリストを取得する
GET /events/12 - 指定した予定の情報を取得する
POST /events - 新しい予定を作成する
PUT /events/12 - 予定 #12 を更新する
PATCH /events/12 - 予定 #12 を部分的に更新する
DELETE /events/12 - 予定 #12 を削除する

完全に同じですね。

いつ何時も、SSL 通信を使おう

SSL必須です。
以下は予定取得をhttpsではなくhttpで実行してみた例です。

httpで実行した時のレスポンス
{
    "error": {
        "errors": [
            {
                "domain": "global",
                "reason": "sslRequired",
                "message": "SSL is required to perform this operation."
            }
        ],
        "code": 403,
        "message": "SSL is required to perform this operation."
    }
}

かっこいい仕様書を作ろう

イケてる仕様書だと思います。

Googleの予定取得の仕様書を見てみましょう。
リクエスト情報やサンプルコード、APIを実行するデモ環境まで用意されています。

またGoogleではDiscoveryAPIというAPIの設計書を取得するAPIまで用意しています。
以下のURLにアクセスしてExecuteを押すとCalendarAPIの仕様を見ることができます。
https://developers.google.com/apis-explorer/#p/discovery/v1/discovery.apis.getRest?api=calendar&version=v3&_h=1&

バージョンは URL に含めよう

含めています。
予定の取得は以下のURLになります。

GET https://www.googleapis.com/calendar/v3/calendars/{calendarId}/events/{eventId}

v3とバージョンが含まれています。

フィルタ・ソート・検索はリクエストパラメータでやろう

リクエストパラメータで行っています。

予定の一覧取得を見てみましょう。
以下の様なリクエストパラメータが定義されています。
* フィルタ:showDeleted, showHiddenInvitations, timeMaxなど
* ソート:orderBy
* 検索:q

レスポンスのフィールドを絞れるようにしよう

絞ることが可能です。
fieldsパラメータを使用します。
?fields=items(id,start,end,summary)のように指定します。

fieldsパラメータを詳しく知りたい方はこちらの記事をどうぞ

作成・更新の後は変更後の情報をフルで返そう

フルで返しています。
以下に予定作成時の例を載せときます。

request:予定の作成のRequest
POST https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events

{
 "end": {
  "date": "2016-07-15"
 },
 "start": {
  "date": "2016-07-15"
 },
 "summary": "テスト予定です"
}
response
{
    "kind": "calendar#event",
    "etag": "\"2937059431494000\"",
    "id": "lqpeq5ih89phe1gmr8l55s2qfk",
    "status": "confirmed",
    "htmlLink": "https://www.google.com/calendar/event?eid=bHFwZXE1aWg4OXBoZTFnbXI4bDU1czJxZmsgYWRtaW5AaG93ZHlsaWtlcy5qcA",
    "created": "2016-07-14T20:55:15.000Z",
    "updated": "2016-07-14T20:55:15.781Z",
    "summary": "テスト予定です",
    "creator": {
        "email": "admin@howdylikes.jp",
        "displayName": "Tatsuya Nakano",
        "self": true
    },
    "organizer": {
        "email": "admin@howdylikes.jp",
        "displayName": "Tatsuya Nakano",
        "self": true
    },
    "start": {
        "date": "2016-07-15"
    },
    "end": {
        "date": "2016-07-15"
    },
    "iCalUID": "lqpeq5ih89phe1gmr8l55s2qfk@google.com",
    "sequence": 0,
    "hangoutLink": "https://plus.google.com/hangouts/_/howdylikes.jp/admin?hceid=YWRtaW5AaG93ZHlsaWtlcy5qcA.lqpeq5ih89phe1gmr8l55s2qfk",
    "reminders": {
        "useDefault": false,
        "overrides": [
            {
                "method": "popup",
                "minutes": 10
            }
        ]
    }
}

HATEOAS を採用するのは待とう

未採用です。
CalendarAPIに限らずHATEOASは筆者の観測範囲では見ていません。

可能な限り JSON で返そう

CalendarAPIなど最近のAPIはJSONを標準で返します。

デフォルトでXMLを返す古いAPIもありますが、GoogleAPIはaltリクエストパラメータでレスポンスを切り替えることができます。
?alt=jsonを指定してあげることでレスポンスがJSONになります。

※逆にCalendarAPIのような新しいAPIの場合は?alt=xmlは指定できません。
GoogleAPIはXMLからJSONへのシフトが完了しつつあります。

フィールドの命名規則を考えよう

GoogleAPIはキャメルケースが基本です。

ここはベストプラクティスとずれましたね。
ベストプラクティスの筆者の考えは、JSONなので原則キャメルケースであるべきだけど、世の中のAPIとしてはスネークケースも多いよとのこと。

JSON はデフォルトで整形しよう

整形されています。

また、GoogleAPIはprettyPrintリクエストパラメータで整形の有無を切り替えることができます。
?prettyPrint=trueがデフォルトで、切りたい場合は?prettyPrint=falseを指定します。

要素はラップせずに返そう

通常はラップしません。エラー時はラップします。

JSONP のリクエストではヘッダを参照する術がありません。しかしながら、世の中のスタンダードは CORS になりつつありますし、RFC 5988 では Link というレスポンスヘッダの仕様が提案されています。つまり、要素をラップする必要はなくなりつつあるわけです。
将来的には、デフォルトの状態ではラップをしていなく、特殊なケースに対応するときのみラップするようになるでしょう。

GoogleAPIはCORSに対応しています。
そのためJSONPにするケースは思いつかない(なにかあるのかな?)のですがcallbackリクエストパラメータでJSONPの対応が可能です。

以下は?callback=hogehogeを指定した時のエラーレスポンスです。
errorでラップされています。

エラー時のJSONP
// API callback
hogehoge({
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "authError",
    "message": "Invalid Credentials",
    "locationType": "header",
    "location": "Authorization"
   }
  ],
  "code": 401,
  "message": "Invalid Credentials"
 }
}
);

どんな状態であれステータスコード 200 を返し、本来のステータスコードはラップした内側に埋め込みます。さらに、従来レスポンスヘッダに入れていたデータも内包させるのです。

GoogleAPIの場合、ステータスコードはエラー時でも200は返しておらず本来のステータスコードと同じステータスコードをレスポンスヘッダに入れているようです。
なので上記の場合、401をレスポンスヘッダに設定されています。

ベストプラクティスとちょっとずれましたね。

追加・更新時のリクエストボディには JSON を使おう

以下のようにJSONを使っています。

予定の作成
POST https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events

{
 "end": {
  "date": "2016-07-15"
 },
 "start": {
  "date": "2016-07-15"
 },
 "summary": "テスト予定です"
}

ページング情報はレスポンスヘッダに入れよう

ページングはレスポンスヘッダには入れてきません。

レスポンス内のnextPageTokenという値を使用します。

一覧取得時に次のページが有る場合のレスポンス
{
    "kind": "calendar#events",
    "nextPageToken": "CigKGnZmMzdnazR1OW1lbjVta2I0dGxnMDExcHYwGAEggIDAv7Lf15wUGg0IABIAGND_ocrr880C"
    // 省略
}

次頁が欲しい場合、?pageToken={nextPageToken}といった形でリクエストパラメータで取得します。

関連データを埋め込む手段を作ろう

CalendarAPIの予定情報の場合、関連データは全て入った状態になっています。

その他のAPIでも関連データが有る場合でも埋め込む手段はありません。
以下のように別でAPIを発行する必要があります。

例)
Googleドライブで複数ファイルのコメントが欲しい時

  1. ファイルの一覧を取得するAPIを実行 -> ファイルIDが取れる
  2. ファイルIDからコメント一覧を取得するAPIを実行 -> コメントが取れる

HTTP メソッドを上書きしよう

可能です。

POSTで送ってX-HTTP-Method-OverrideヘッダにPUTDELETEなどを指定します。

リクエスト制限情報はレスポンスヘッダに入れよう

これは行っていません。

制限情報やリクエスト数はGoogle Developer Consoleを見る必要があります。

使用量

image

割り当て

image

トークン認証を使おう

トークン認証で行います。
※昔はアカパス認証があったのですが今はないはず。

AuthorizationヘッダにBearer {Access Token}という形で指定します。
またaccess_tokenoauth_tokenをリクエストパラメータにすることも可能です。
?access_token={Access Token}
?oauth_token={Access Token}

キャッシュの情報をレスポンスヘッダに入れよう

これについて、ETag を使うやり方と Last-Modified を使うやり方があります。

Etagを使用しています。
※ダブルクォートが含まれるのに注意

response例
{
    "kind": "calendar#event",
    "etag": "\"2937059431494000\"",
    "id": "lqpeq5ih89phe1gmr8l55s2qfk",
    "status": "confirmed",
// 省略
}

また元記事(翻訳前の記事)ではキャッシュしか触れられていませんが、ここに触れるなら排他制御も含めたほうがいいと思われるので記載しておきます。

キャッシュ

取得時にEtagをIf-None-Matchで指定することで 変更されてない場合に304 Not Modifiedを受け取ることができます。

排他制御

更新・削除時にEtagをIf-Matchで指定することで 変更されていた場合に412 Precondition Failedを受け取ることができます。

ちゃんとしたエラーメッセージを返そう

これはちゃんと返していると思います。

エラー時のレスポンス
{
    "error": {
        "errors": [
            {
                "domain": "global",
                "reason": "conditionNotMet",
                "message": "Precondition Failed",
                "locationType": "header",
                "location": "If-Match"
            }
        ],
        "code": 412,
        "message": "Precondition Failed"
    }
}

まれに500 Internal Server Errorを返します(笑

余談

ごくまれ(Google側の障害とか)に ロボットの画面を表示するhtmlを返します。
Twitterでいうクジラ、Githubでいうユニコーンですかね。

その場合パースするライブラリがJson Parse Errorみたいなのを吐くので注意。

ちなみにこんなやつです。
image

HTTP ステータスコードを有効活用しよう

そこそこ活用していると思います。

見たことあるやつを緑色で塗っときます。
201は使っておらず200で代用していますね。

見たことあるやつを緑色で塗る
+ 200 OK - GET, PUT, PATCH, DELETE リクエストが成功した場合に応答。もしくは、POST リクエストが結果的に何もリソースを作らなかった場合に応答。
201 Created - POST リクエストがリソース作成に成功した場合に応答。なお、そのリソースへのリンクを Location ヘッダに含める必要がある。
+ 204 No Content - 成功したDELETE リクエストで、ボディを返したくない場合に応答
+ 304 Not Modified - HTTP キャッシュが有効な場合に応答
+ 400 Bad Request - パース不可能なリクエストボディが来た場合に応答
+ 401 Unauthorized - 認証がされていない、もしくは不正なトークンの場合に応答
+ 403 Forbidden - 認証はされているが、認可されていないリソースへのリクエストに応答
+ 404 Not Found - 存在しないリソースへのリクエストに応答
405 Method Not Allowed - 認可されていないメソッドでのリクエストに応答
410 Gone - 今は存在しないリソース(廃止されたAPIなど)で空要素を返す場合などに応答
415 Unsupported Media Type - 対応していない MediaType が指定された場合に応答
422 Unprocessable Entity - バリデーションエラーに対して応答
+ 429 Too Many Requests - 回数制限をオーバーしたリクエストに対して応答

あとがき

大体満たしていると言っていいのではないでしょうか。
TwitterやFacebookなどの有名どころはどうなっているんでしょうね。使ったことある人に書いて欲しいなぁ。
わたし、気になります。