HTML
Web
RESTful

《REST思想》と《リソース指向》と《Webページ》を一緒にしてはいけない

More than 3 years have passed since last update.

はじめに

Ruby on Rails や同種のフレームワークを使っていると、《REST思想》と《リソース指向》と《Webページ》がごちゃまぜになったWebアプリケーションをつい設計してしまいます。
三つの違いを意識し、適切なWebアプリケーションを作成するようにしましょう。でないと後悔することになります。

なお、この三つの用語は本来の意味とずれているかもしれません。
「コメント」、「編集リクエスト」大歓迎です。

解説

http://yourhost/books のURLで本の一覧が取得できるようなWebサービスを提供するとします。
では /books を含めた各URLはどのように振る舞うべきなのでしょうか。

(URLと言っている部分でも実際はpathを指している場合があります。ご了承ください)

REST思想

本に対してCRUD操作を行うインタフェースとして、以下のようなものを提供します。

役割 method + URL
本一覧を取得 GET /books
一つの本を取得 GET /books/1
新しい本を追加 POST /books
既存の本を修正 PUT /books/1
既存の本を削除 DELETE /books/1

各本の情報はJSONで表現されます。/books にGETリクエストした場合はレスポンスとして本の一覧がJSONとして返されます。

本の追加や削除を行う場合は、本情報をJSON形式でPOSTリクエストのボディとして送ります。application/x-www-form-urlencoded形式で送ることは避けるべきです。
更新に失敗した場合、失敗した理由をJSON形式で返します。
ステータスコードは、取得時・更新成功時には200 OKを、更新失敗時には4XX番代を使うのがよいでしょう。

情報はJSON形式と書きましたが、XMLなどでも構いません。
しかし、正常系・異常系ともにアプリケーション内で統一された形式を利用してください。

リソース指向

本の情報は/booksにあるとして、拡張子を変えることで、様々なフォーマットでデータを利用できるようにします。
例えば本の一覧をJSON形式で欲しければ/books.jsonにリクエストし、csv形式で欲しければ、/books.csvにリクエストします。
/books.xlsのようにバイナリを提供してもかまいません。

更新用のAPIは用意しない方が良いでしょう。
/books.csvに対する更新を行う場合は、はPOSTリクエストのボディとして何を送ればよいのでしょうか。CSV形式で良いのでしょうか。JSONでしょうか。
また、更新に失敗したときのエラーはどう返すべきでしょうか。もし/books.csvにリクエストししたにも関わらず、JSON形式でエラーを返ってくることになると、クライアントは苦労するでしょう。

Webページ

サーバはHTMLを返し、ブラウザはAjaxを使わずHTML FormでデータのCRUDを行います。

Webページの場合、RESTと違い以下のようなインタフェースを提供すべきです。

役割 URL
一覧画面を表示 GET /books
詳細画面を表示 GET /books/1
追加formを表示 GET /books/new
追加アクション POST /books/new
編集formを表示 GET /books/1/edit
編集アクション POST /books/1/edit
削除確認画面を表示(任意) GET /books/1/delete
削除アクション POST /books/1/delete

REST同様にCRUD用のAPIを提供しますが、Webページの場合は更新Formを表示するためのページが必要になります。
さらに更新に失敗した際、適切にエラーを見せるには

  • 更新FormページのURLとPOST先のURLを同じにする
  • 成功した場合、一覧ページあるいは詳細ページにリダイレクトする
  • 失敗した場合、エラーを表示し、Formを直前の入力で埋める

のがよいでしょう。

URLを同じにすることで、CSSなどのリソースのパスや、リクエストパスに依存したコードを書きやすくなります。
成功時にリダイレクトすることで、F5対策になります。リダイレクトには303 See Otherを利用してください。
失敗時にリダイレクトせずそのまま表示することで、エラーや直前の入力をFormに表示しやすくなります。

HTML FormではリクエストメソッドとしてGETかPOSTしか利用できません。取得はGET、更新はPOSTを利用しましょう。わざわざ_method=putのようなリクエストパラメタを利用しても、得られるものはありません。

まとめ

同じURL /books であっても様々な設計があるということを理解してもらえたと思います。

この三つ全てをまとめて提供することは可能です。しかしControllerのコードは複雑になり、一部の機能はほとんど使われず、削除したくても一度提供したので消せないという結末に終わります。

もし複数提供する必要がある場合、URLを別にするのが良い解決案です例えば以下のように分けることができます。
(※ ただしドメイン直下は常にWebページを提供するのが良い)

目的 URL
REST http://yourhost/api/books
リソース http://yourhost/books
WEBページ http://yourhost/web/books

要件にあわせ、素敵なアプリケーションを設計してください。