HerokuのAPIデザイン

by Taichi Nakashima,

Herokuが自ら実践しているAPIデザインガイドをGithubに公開した.

“HTTP API Design Guide”

このガイドは些細なデザイン上の議論を避けて,ビジネスロジックに集中すること目的としている.Heroku特有なものではなく,一般にも十分適用できる知見となっている.

最近は,モバイル向けにAPIをつくることも多いため,勉強もかねて抄訳した.なお内容は,HTTP+JSONのAPIについて基本的な知識があることが前提となっている.

適切なステータスコードを返す

それぞれのレスポンスは適切なHTTPステータスコード返すこと.例えば,“成功"を示すステータスコードは以下に従う.

  • 200: GETDELETEPATCHリクエストが成功し,同時に処理が完了した場合
  • 201: POSTリクエストが成功し,同時に処理が完了した場合
  • 202: POSTDELETEPATCHリクエストが成功し,非同期で処理が完了する場合
  • 206: GETのリクエストは成功したが,レスポンスがリソースに対して部分的である場合

その他のクライアントエラーやサーバエラーに関しては,RFC 2616を参照(日本語だと,このサイト“Webを支える技術”が詳しい).

可能な全てのリソースを提供する

そのレスポンスで可能な全てのリソース表現(つまり,全ての要素とそのオブジェクト)を提供すること.ステータスコードが200もしくは201のときは常に全てのリソースを提供する.これはPUTPATCHDELETEリクエストでも同様.例えば,

$ curl -X DELETE \
  https://service.com/apps/1f9b/domains/0fd4
HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
...
{
  "created_at": "2012-01-01T12:00:00Z",
  "hostname": "subdomain.example.com",
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "updated_at": "2012-01-01T12:00:00Z"
}

ステータスコードが202の場合は,完全なリソース表現は含めない.例えば,

$ curl -X DELETE \
  https://service.com/apps/1f9b/dynos/05bd
HTTP/1.1 202 Accepted
Content-Type: application/json;charset=utf-8
...
{}

リクエストボディ中のシリアル化されたJSONを受け入れる

フォームデータに加えて,もしくは代わりに,PUTPATCHPOSTのリクエストボディ中のシリアル化されたJSONを受け入れること.これにより,リクエストとレスポンスが対称になる.例えば,

$ curl -X POST https://service.com/apps \
    -H "Content-Type: application/json" \
    -d '{"name": "demoapp"}'    
{
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "name": "demoapp",
  "owner": {
      "email": "[email protected]",
      "id": "01234567-89ab-cdef-0123-456789abcdef"
  },
  ...
}

リソースの(UU)IDを提供する

それぞれのリソースにデフォルトでid要素を提供すること.特別な理由がない限りUUIDを使うこと.サービスの他のリソースの中で一意でないidを使わないこと.

小文字かつ8-4-4-4-12フォーマットを使うこと.例えば,

"id": "01234567-89ab-cdef-0123-456789abcdef"

タイムスタンプを提供する

デフォルトでリソースのcreated_atupdated_atのタイムスタンプを提供すること.例えば,

{
  ...
  "created_at": "2012-01-01T12:00:00Z",
  "updated_at": "2012-01-01T13:00:00Z",
  ...
}

これらのタイムスタンプは,削除されうるリソースには必要ないかもしれない.

時刻はISO8601フォーマットのUTCを使う

時刻はUTC(協定世界時)のみを返答する,もしくは受け入れること.ISO 8601フォーマットを用いること.例えば,

"finished_at": "2012-01-01T12:00:00Z"

一貫したパス名を使う

リソース名:リソース名には複数形を使う.ただし,要求されるリソースがシステム全体でシングルトンである場合は,単数形を使う(例えば,ほとんどのシステムではユーザはただ1つのアカウントのみを持つ).これにより,リソースへの参照方法に一貫性を持たせることができる.

アクション名:パスの末尾にリソースに対する特別なアクションを必要としないのが望ましい.必要な場合は,それを明確にするため,以下のようにアクション名をactionsの後に続けて記述できるようにする.

/resources/:resource/actions/:action

例えば,

/runs/{run_id}/actions/stop

パス名と要素名には小文字を使う

ホスト名に合わせて,パス名には小文字,区切り文字には-を使うこと.例えば,

service-api.com/users
service-api.com/app-setups

同様に要素名にも小文字を使うこと.Javascriptで使うことを考慮して,区切り文字には_を使うこと.例えば,

"service_class": "first"

外部キーの参照はネストする

外部キーによる一連の参照はネストして記述する.例えば,

{
  "name": "service-production",
  "owner_id": "5d8201b0...",
  ...
}

とするのではなく,以下のようにする.

{
  "name": "service-production",
  "owner": {
      "id": "5d8201b0..."
  },
  ...
}

これにより,レスポンスの構造を変更したり,トップレベルのフィールドを追加することなく,関連した情報を含めることができる.例えば,

{
  "name": "service-production",
  "owner": {
      "id": "5d8201b0...",
      "name": "Alice",
      "email": "[email protected]"
  },
  ...
}

id以外の参照方法をサポートする

ユーザにとってリソースの特定にidを使うのが不便な場合がある.例えば,ユーザはHerokuアプリケーションをUUIDではなく,名前で見分けているかもしれない.このような場合を考慮して,idと名前の両方でリソースにアクセスできるとよい.例えば,

$ curl https://service.com/apps/{app_id_or_name}
$ curl https://service.com/apps/97addcf0-c182
$ curl https://service.com/apps/www-prod

ただし,名前のみでしかアクセスできないといったことは避ける.

構造的なエラーを生成する

エラーの際は,一貫した構造的なレスポンスを生成すること.レスポンスには,コンピュータが解釈しやすいエラーidと,ユーザが理解しやすいエラーmessageを含めること.さらに,エラーとその解決方法を示すさらなる情報へのurlも含めるとよい.例えば,

HTTP/1.1 429 Too Many Requests
{
  "id":      "rate_limit",
  "message": "Account reached its API rate limit.",
  "url":     "https://docs.service.com/rate-limits"
}

エラーのフォーマットと,idのドキュメントを作成すること.

Etagによるキャッシュをサポートする

返答するリソースのバージョンを示す,ETagヘッダを含めること.クライアントはIf-None-Matchヘッダで,キャッシュが最新であるかをチェックできるようにするべき.

Request-Idでリクエストを追跡する

UUIDによるRequest-IdヘッダをそれぞれのAPIのレスポンスに含めること.サーバとクライアント両方でその値のログをとれば,リクエストのデバッグの際に有用になる.

Content-Rangeでレスポンスを分割する

データ量が大きくなりそうな場合は,レスポンスを分割すること.Content-Rangeヘッダを使って,コンテンツの範囲を指定できるようにする.リクエストとレスポンスのヘッダ,ステータスコードなどは“Heroku Platform API on Ranges”の例に従うこと.

制限の状態を示す

クライアントからのリクエストを制限することで,サービスが不安定になることを防ぐこと.リクエストの制限にはトークンバケットアルゴリズムが使える.

RateLimit-Remainingヘッダを使って,リクエストトークンの残量を返答すること.

Acceptsヘッダーでバージョニングする

始めからAPIをバージョニングすること.Acceptsヘッダーを使ってバージョンを指定する.例えば,

Accept: application/vnd.heroku+json; version=3

デフォルトのバージョンを持たないほうがよい.クライアントに対して特定のバージョンを指定することを明示的に指示する.

パスのネストを最小限にする

ネストした親子関係をもつリソースのデータモデルでは,パスは深くネストすることになる.例えば,

/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}

ルートパスにリソースを配置するようにパスのネストの深さを制限すること.ネストをある特定の範囲の集合を示すために使うこと.例えば,上の例では,1つのdynoは1つのappに属し,1つのappは1つのorgに属する,

/orgs/{org_id}
/orgs/{org_id}/apps
/apps/{app_id}
/apps/{app_id}/dynos
/dynos/{dyno_id}

コンピュータが理解しやすいJSONスキーマを提供する

コンピュータが理解しやすいJSONスキーマを提供すること.prmdを使ってスキーマを管理し,prmd varifyでそれを評価すること.

ユーザが理解しやすいドキュメントを準備する

クライアントの開発者がAPIを理解できるように読みやすいドキュメントを準備すること.

prmdを使ってスキーマを作成したなら,prmd docで簡単にmarkdown形式のドキュメントを生成できる.

これに加えて,以下のようなドキュメントを準備すること.

  • 認証方法.認証トークンの取得方法
  • APIの安定性とバージョン.望ましいバージョンの選択方法
  • 共通のリクエスト/レスポンスヘッダ
  • エラーのフォーマット
  • 様々な言語によるAPIの利用例

実行可能な実例を提供する

ユーザがターミナルからAPIの動作を確認できるように,実行可能な実例を提供すること.APIを試すための手順をできる限り最小限にする.例えば,

$ export TOKEN=... # acquire from dashboard
$ curl -is https://$TOKEN@service.com/users

prmdmarkdownのドキュメントを生成していれば,例も同時に得られる.

安定性を説明する

APIの安定性を説明すること.例えば,プロトタイプか,開発中か,プロダクションレベルかを示す.

プロダクションで利用可能である,もしくは安定であることを宣言したら,そのバージョンで後方互換を崩すような変更を加えないこと.後方互換を崩す場合は,バーションを上げること.

詳細はHeroku API compatibility policyを参考に.

SSLを必須にする

APIへのアクセスはSSLを必須にすること.いつSSLを使い,いつ使わないかではなく,だた常にSSLを必須にする.

デフォルトでJSONを整形する

ユーザが初めてAPIを使うときは,おそらくコマンドラインからcurlを使う.コマンドライン上でレスポンスが整形されていれば,ユーザはAPIをより理解しやすくなる.例えば,

{"beta":false,"email":"[email protected]","id":"01234567-89ab-cdef-0123-456789abcdef","last_login":"2012-01-01T12:00:00Z", "created_at":"2012-01-01T12:00:00Z","updated_at":"2012-01-01T12:00:00Z"}

ではなく,以下のようにレスポンスを出力する.

{
  "beta": false,
  "email": "[email protected]",
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "last_login": "2012-01-01T12:00:00Z",
  "created_at": "2012-01-01T12:00:00Z",
  "updated_at": "2012-01-01T12:00:00Z"
}

Licence