オブジェクトの広場はオージス総研グループのエンジニアによる技術発表サイトです

クラウド/Webサービス

これから始めるエンタープライズ Web API 開発

第2回 使いやすいREST API
オージス総研
窪田 康大
2015年11月5日

本連載では、Web APIの公開/構築に興味のある方向けに、Web APIの設計や実装の課題とその解決策をご紹介します。

第1回の記事ではWeb APIの設計について、下記の3点を意識するべきという解説をしました。

  • 使いやすさ、わかりやすさ
  • 安全性、堅牢性の高さ
  • 耐障害性の高さ

今回は主に『使いやすさ、わかりやすさ』について、REST APIを題材に解説していきたいと思います。

なお本記事は『REST APIは利用したことはあるが、自身で設計したことが無い』といった開発者を想定しています。 そのため『REST APIとは』のようなREST APIの概要には触れません。 REST APIの基本的な内容を学習したい方は下記書籍などをご参照頂くと良いかと思います。

『RESTful Web サービス』(Leonard Richardson他 著, 山本洋平 監訳, オライリー・ジャパン社, 2014年11月)

https://www.oreilly.co.jp/books/9784873113531/

使いやすいREST APIとは

使いやすいREST APIを設計する際に遭遇する課題にはどのようなものがあげられるでしょうか。 それを知るにはApigee社が公開している「Apigee Web API Design」というドキュメントが大変役立ちます。 このドキュメントのPDFファイルが下記URLから入手可能です。

Apigee Web API Design(Brian Mulloy著, Apigee社):

https://apigee.com/about/resources/ebooks/web-api-design

副題に『開発者が愛するインタフェース』と題されたこのドキュメントには、使いやすいAPIを設計するために検討するべき事項が簡潔にまとまっています。 わずか38ページで、公開されたのは3年以上前ですが、いま読んでも色褪せない素晴らしい内容です。

そこで今回は、このドキュメントの内容に沿う形でREST APIの設計について解説していきたいと思います。 なお、最近の動向のご紹介や本記事のボリュームの都合のため、一部ドキュメントとは異なっていることをご了承ください。

リソース名

REST APIではURLでリソースを表現し、そのリソースへの操作をHTTPメソッドを用いて表現します。

操作内容 URL METHOD
ユーザ一覧の取得 /users GET
ユーザの新規登録 /users POST
ユーザ情報の取得 /users/:id GET
ユーザ情報の更新 /users/:id PUT
ユーザ情報の削除 /users/:id DELETE

このときリソース名には、なるべく具体的な複数形の名詞を名称として付けることが推奨されます。 複数形を用いることで/users/:idのようにIDでリソースを特定する操作も直観的に理解しやすくなります。 ただし複数形であっても/items/assetsのような抽象度の高い単語は望ましくありません(具体的なリソースが確実にイメージできる場合を除く)。 開発者に直観的に訴えかけるように/blogs/videosのように具体的な名称にするべきでしょう。

リソースの関連を単純にする

他のリソースと全く関連を持たないリソースは比較的少なく、多くは何らかの関連を持っています。 例えば、ある部門に属する複数の社員は下記のように表現することができます。

GET  /departments/12/employees

これに会社や顧客といった関連するリソースもURLに長々と追加すれば表現できますが、長すぎるインタフェースはシンプルさを阻害します。 わかりやすさを損なわないために、2階層より深い階層を表現するURLは利用しないようにしましょう。

なお、上述のリソースの関連をあらわすURLの操作は下記のようになります。

操作内容 URL METHOD
部門に所属する社員の一覧取得 /departments/:id/employees GET
配属・着任 /departments/:id/employees POST
離任 /departments/:id/employees/:id DELETE

このような設計にすることで、リソースの関連操作をイメージしやすくなります。

実際にはdepartmentsemployeesの間には関連テーブル相当の情報があるかもしれませんが、必ずしもDBスキーマのような実体を忠実にREST APIへ反映する必要はありません。 REST APIを利用する開発者にとって直観的でなければ、関連テーブルをリソースとして公開する必要はありません。

エラー処理

実践的な観点では、REST APIにおいてサーバで何が起こっているのかをなるべく正確にクライアントに伝え、問題解決への手がかりを提供するよう努力するべきです。

HTTPステータスコードを活用する

REST APIでサーバの処理結果を伝えるには、HTTPステータスコードを活用するべきです。 HTTPステータスコードの1ケタ目を参照するだけで、サーバの処理結果についておおよその分類ができます。

ステータスコード 意味
100番台 情報
200番台 成功
300番台 リダイレクト
400番台 クライアントが原因のエラー
500番台 サーバが原因のエラー

HTTPステータスコードは70個以上に細かく分類されており、より具体的な処理結果を知る手掛かりとなります。 なかにはWebDAVの拡張ステータスコードや予約コードなども含まれているので、ここではREST APIで頻繁に利用されるものを下表にまとめました。

ステータスコード 名称 意味
200 OK リクエストの処理は成功した。
201 Created リクエストの処理が完了し、新たなリソースが作成された。POSTやPUTの応答で利用する。
204 No Content リクエストを受理したが応答で返すコンテンツが無い。DELETEの応答として利用することが多い。ただし使うべきではないという意見や、PUTやPATCHの応答で利用するべきなど様々な意見がある
301 Moved Permanently リソースは恒久的に移動した。
304 Not Modified リソースは未更新。
400 Bad Request リクエストが不正。
401 Unauthorized 認証が必要。
404 Not Found リソースが見つからない。単にアクセス権が無い場合でも利用されます。
409 Conflict リソースが矛盾(IDが衝突など)した。
500 Internal Server Error サーバサイドでエラーが発生した。
503 Service Unavailable サーバが一時的に停止している。

これら全てを必ず網羅する必要はありませんし、逆にこれ以外のHTTPステータスコードは不要だということでもありません。 上記以外のHTTPステータスコードも必要に応じて上手く活用できるよう、よく検討を重ねて下さい。 なおHTTPステータスコードの一覧については下記リンク先を参照して頂くと良いと思います。

HTTPステータスコード Wikipedia

https://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89

人間のためのメッセージ

HTTPステータスコードに加えて、クライアントアプリケーションのためだけではなく、人間のために状況を詳しく伝えることが望ましいです。 何が原因でエラーを引き起こしたのか、クライアントアプリケーションの開発者のヒントになる情報を少しでも多く提供しましょう。

{
  "developerMessage" : "...",
  "userMessage" : "...",
  "errorCode" : 12345,
  "moreInfo": "https://dev.teachdogrest.com/errors/12345"
}

なお、より詳しい情報へのリンク(上述の例のmoreInfoに相当する情報)を追加することが推奨されます。

APIのバージョニングのためのヒント

REST APIでバージョンを指定する方法は大きく3つあります。

URLに含める

一般的に多いのはこの『URLにバージョンを含める』パターンでしょう。

https://api.your.domain/v1/employees

SalesforceやTwitter、Apigeeなど多くのサービスで採用されている方式です。 この方式のメリットはURLを見るだけで、そのAPIがどのバージョンのものなのか直観的に把握できることでしょうか。

クエリパラメータに含める

わかりやすさで言えば下記のように『クエリパラメータにバージョンを含める』のも1つの手です。

https://api.your.domain/employees?version=1

これはAmazonなどで採用されている方式で、URLに含めるパターンと決定的に違うのは省略可能だということです。 そのため、デフォルトのバージョン変更する際に利用者への周知が十分にできていないとトラブルにつながる危険性があります。 実際の運用を考慮すれば、クエリパラメータでのバージョン指定は必須であるべきです。 常に最新のバージョンを利用したい場合のみ、省略するよう利用者に十分周知しトラブルを未然に防ぐ努力をしましょう。

HTTPヘッダに含める

また『HTTPヘッダにバージョンを含める』パターンも根強い人気があります。 Googleの各種APIではGdate-Versionというヘッダを用いてバージョンを指定できるようになっています。

Gdata-Version: 3.0

本来、URLは純粋にリソースをあらわすものなので、HTTPヘッダを利用するのが最も理にかなっているでしょう。 一方で、ブラウザやcurlコマンドで気軽にAPIにアクセスしたいと思った時、『URLにバージョンを含める』パターンに比べて少し面倒に感じるかもしれません。

バージョニングで考えること

筆者はバージョンをURLに含めるのが好みですが、上記3つのいずれの方式を採用するかは一長一短でもあるので、より良いと思うものを採用してください。

本記事ではREST APIでバージョンをどう表現するかという観点の議論にとどめますが、実際にはAPIのライフサイクルをどう管理するかという運用面での問題に大きく頭を悩ませることでしょう。 各バージョンのAPIを利用可能な期間はどのくらいにするか。 古くなったAPIの提供を終了するとき、ユーザにはどうやってアナウンスするのか。 あるバージョンのAPIに重大な脆弱性などが発見された場合、どのような対応を実施するのか。 このようなAPIの運用に関する議論は別の機会に深堀していきたいと思います。

複数フォーマットのサポート

APIでやり取りされるメッセージのフォーマットとしてはXMLやJSON、CSVなど様々なフォーマットが利用できますが、API利用者にとって必要なものを対応しておきましょう。 その際、デフォルトのフォーマットはREST API全体で一貫させておくべきです。

なお、バージョンを表現するのと同じく、フォーマットを指定する方法にも3通りの方法があります。

URLの末尾に拡張子で指定する

GET /employees.json

クエリパラメータで指定する

GET /employees?format=json

HTTPヘッダで指定する。

Accept: application/json

ページネーションと部分的な応答

冒頭の一覧表でも記載しましたが、GET /usersであれば複数件のリソース取得をあらわし、GET /users/:idであれば特定のリソース取得をあらわします。 しかし実践において『1件か全件(または固定の上限件数)か』の二択でアプリケーションを構築するのは非効率です。 クライアントアプリケーションが、必要とする情報だけを取得できるような仕組みを提供するべきです。

例えば1000人の社員が在籍する企業で、ある10人の社員情報が必要だったとします。 10人分の情報を取得するために1000人分の情報をまとめて取得するのも、わざわざ10回もAPI呼び出し繰りかえして1人1人の情報を取得するのも、良いやり方とは言えません。

例えば下記のように、指定した範囲のリソースを取得可能にすることで、ページ送りのような画面を構築しやすくなります。

/employees?offset=101&limit=25

このときリソースの総数をクライアントが把握できるよう、メタデータをレスポンスに含めると良いでしょう。 メタデータを含めないのであれば、リソースの総数を返すような仕組みを別途考える必要があります。

{
  "employees" : [
    // -- 省略  --
  ],
  "metadata" : [
    {
      "totalCount" : 327,
      "limit" : 25,
      "offset" : 101
    }
  ]

非連続な複数の値を取得したい場合は、下記のようにIDをカンマ区切りで指定するのも一つの手です。 これによりAPIの呼び出し回数を削減することができます。

/employees/12,33,36

APIの呼び出し回数は削減ができても、まだレスポンスには不要な情報が残っているかもしれません。 例えば、社員の氏名と年齢を取得したいだけなのに、住所や電話番号を含む全ての社員情報を毎回取得するのは非効率です。 下記のように必要なフィールドを指定できるようにしておくことで、ネットワーク帯域の無駄遣いを軽減できるようになります。

/employees?fields=name,age

URLに動詞を使う場合

ここまでリソースが名詞であることを前提に説明をしてきましたが、下記のように動詞でなければ表現しにくいものも存在します。

  • 計算
  • 翻訳
  • 変換処理

例えば、下記のような日本円から米ドルへの為替計算などがそれにあたります。

GET /convert?from=JPY&to=USD&amount=100

また特定のリソースや、複数のリソースを横断して検索したい場合などは、/searchといった動詞をURLに使います。

GET /employees/search?q=hoge+fuge
GET /search?q=hoge+fuge

属性名

REST APIのレスポンスに含まれるJSONの属性名に規約はないため、サービスによってマチマチですが、JavaScriptの規約に従ってキャメルケース(CamelCase)を使うのが望ましいでしょう。 下記のように開発者がストレスを感じにくいJavaScriptのソースコードを記述できるようになります。

myObject = { "createdAt": 1320296464 }

timing = myObject.createdAt;

例外的な取扱い

クライアントがHTTPステータスコードを正しく処理できない場合

エラー処理の節で解説したとおり、REST APIは原則、適切なHTTPステータスコードを返すべきですし、クライアントもそれらのコードに適した実装をするべきです。 しかしながら、HTTPステータスコードを正しく処理できないクライアントに対しては相応の対応が必要です。

例えばJSONPにはHTTPステータスコードに対応できないという問題があります。 これに対応するには、下記のようにHTTPステータスコードをレスポンスのペイロードに含めると良いでしょう。

{
  "statusCode" : 401,
  "developerMessage" : "...",
  "userMessage" : "...",
  "errorCode" : 12345,
  "moreInfo" : "https://api.your.domain/errors/12345"
}

もしくは下記のようにメタデータにHTTPステータスコードを含めてレスポンスを返しても良いかもしれません。

{
  "metadata" : {
    "statusCode" : 401
  },
  "response" : {
    "developerMessage" : "...",
    "userMessage" : "...",
    "errorCode" : 12345,
    "moreInfo" : "https://api.your.domain/errors/12345"
  }
}

クライアントのHTTPメソッドサポートに制限がある場合

古いブラウザではHTTPメソッドのうち、GETとPOSTにしか対応していないものがあります。 GET/POST/PUT/PATCH/DELETEの5つのHTTPメソッドを利用できるようにするためにRuby on Railsの開発者が使う方法などが参考になります。

操作 URL MEHOD
新規作成 /employees?method=post GET
参照 /employees または /employees?method=get GET
更新 /employees?method=put または /employees?method=patch GET
削除 /employees?method=delete GET

HTTPメソッドは常にGETですが、5つのHTTPメソッドが全て利用できるようなります。

一点、注意しなければならないのは、全てGETで公開されているゆえにGooglebotのようなクローラーが呼び出してしまう可能性があることです。 意図しないAPIアクセスによってコンテンツが破壊されないよう、十分理解したうえでこれらの機能を提供するべきです。

おしゃべりなAPI(Chatty API)

おしゃべりなAPIとは、単純なUIを組み立てるためだけにサーバに対して数十あるいは数百回の呼び出しを必要とするようなAPIのことをいいます。 このようなAPIは、そもそも設計に問題があり非効率な呼び出しをクライアントに強制していると考えられます。

本記事の内容をふまえ、使いやすいREST APIの設計を改めて検討してください。 クライアントの開発者がどのようにAPIを利用するかを想像し、不必要なAPIを呼び出しを強要することが無いか十分に確認するよう心掛けて下さい。

関連するリソースの取得をサポート

REST APIの設計に大きな問題が無くとも、クライアントが複数リソースを合成したデータを頻繁に必要とする場合、結果的におしゃべりなAPIになってしまうことがあります。 リソースを扱う際に、関連する他のリソースの情報も必要とするため、クライアントは複数のリソースのAPIの呼び出しを強いられるためです。

これを解決するには、下記のように複数リソースをまとめたレスポンスを提供できるようにすると良いでしょう。 構文をドット表記しておくことで深い階層のデータにも対応できるようになります。

GET /departments/12?fields=name,employees.name,employees.phone
{
  "name" : "クラウドインテグレーションサービス部",
  "employees" : [
    {
      "name"   : "KubotaYasuhiro",
      "phone"  : "090-xxxx-xxxx"
    }
    // -- (省略:社員情報)
  ]
}

オーケストレーション層

複雑なアプリケーションを構築する場合を考えてみましょう。 リソースを跨ぐようなトランザクション管理や、複雑なUIを構築するために多くのAPI呼び出しを必要とするかもしれません。

このような複雑な要求には、下図のようにクライアントごとに最適化されたREST APIで対応すると良いでしょう。

2

複数のAPI呼び出しを1つにまとめるなど、クライアントごとに最適化することで、より少ないAPI呼び出しで効率的なデータのやり取りができます。 一方で共通的なREST APIは綺麗な設計を保つことができますし、アプリケーションごとのリリースサイクルに合わせてAPIを修正することも容易になるでしょう。

なお、オーケストレーション層を構築する場合は、下記のようなステップで進めていくとクライアントの開発者と協調しながら効率的に作業が進められます。

  1. クライアントにとって理想的なオーケストレーション層のREST APIを設計する。
  2. スタブを使ってREST APIを実装する。バックエンドにある汎用的なREST APIと接続する前でも、クライアントの開発者はこのREST APIを利用することができます。
  3. バックエンドの汎用的なREST APIと統合する。

オーケストレーション層についてはNetflixの記事で具体的な方法が紹介されていますので、こちらも是非参考にして下さい。

The future of API design: The orchestration layer(Daniel Jaconson著, 2013年12月)

https://thenextweb.com/dd/2013/12/17/future-api-design-orchestration-layer/

SDKの提供

十分に洗練されたREST APIと、十分なドキュメントがあれば、クライアントの開発者はSDKが無くとも困らないかもしれません。 その一方で、SDKの提供がREST APIを提供する側と利用する側、双方にメリットをもたらすケースも少なくありません。

例えば、優れたREST APIでも利用方法を誤ればその力を十分に発揮することはできません。 REST APIの提供側はSDKを提供することで、自分たちが理想とするAPIの使い方をクライアントに強制できます。 結果、非効率なAPI呼び出しを削減し、ネットワーク帯域やサーバへの負荷を軽減させることができます。

また、REST APIの利用者にとっても、実装を強制されることで低品質なソースコードが削減されるのは喜ばしいことです。 プラットフォームごとに最適化されたSDKを使うことで、クライアントにAPI呼び出しを実装する手間も削減できるでしょうし、SDKに付属される様々なツールを用いることで開発をスムーズに進められるかもしれません。 Amazon Web Serviceでも多くのWeb APIを提供していますが、やはりそれを補完するようにCLI(コマンドラインインタフェース)やWebブラウザ・Android・iOSといった各種デバイスごとのSDKが提供されています。 開発者はこれらのSDKを用いることで、容易にAWSが提供するサービスをプログラムで制御できるようになります。

1

エンタープライズを意識する

一般的なREST APIの考え方について解説してきましたが、エンタープライズでの利用を想定すると、これだけでは足りないかもしれません。 高い相互運用性が求められるエンタープライズシステムでは、かつてのWSDLのような仕組みへの要望が根強く残っています。

このような要望に対する1つの答えとしてRAMLが注目を集めており、次回の記事はこのRAMLについて紹介していきたいと思います。

参考文献

上記はいずれも素晴らしい文献ですので、REST APIを深くご理解頂くためにも、是非ご一読下さい。