RESTful API のおさらい

前後リンク

REST の歴史

REST (REpresentational State Transfer) という言葉は 2000 年に Roy Fielding の博士論文で初出しました。

(思想としてはその前からあった? REST 入門)

日本では 2005 年ぐらいから徐々に流行りだして、 2006 年に WEB+DB Press で特集や連載が組まれる等が行われ、 (Rails 2.x が RESTful を打ち出した) 2007 年の終わりには web 開発者の間では一般化した言葉になっていたって印象。

なぜ REST が必要になったのか

はるか昔はメインフレーム上で全部入りのアプリケーションを開発していたので何も問題はなかった。

異ベンダーのアプリケーション間で連携を取るようになり、 また、クライアントサーバモデルというシステム構成が普及。

通信インタフェースの適切な手法が求められた。

そこで RPC (Remote Procedure Call) って話になり SOAP や WSDL、WS-* といった技術が出てくるんだが、 その流れに乗らずに「HTTP、URI といった Web 標準を上手に使えば大丈夫」という人が現れる。

これが REST の始まり。

Web 標準を上手に使い、REST の原則に沿ったアプリケーションにしていると

ものになる。

SOAP みたいな RPC は何でもできるけど重厚なので最近は (少なくとも web 系では) 落ち目。

余談だけど「Web サービス」って言うと SOAP の文脈の方の Web Service かと思っちゃうので なるべく「Web アプリケーション」と呼称したい派です。

REST を 3 行で言うと

例えばどういうこと?

この記事を見るのにはブラウザだと URL を開くが、telnet からだと

> telnet blog.onk.ninja 80
GET /2017/09/21/review_of_restful_api HTTP/1.1
Host: blog.onk.ninja

を実行する。

ブラウザで URL を開く場合、これと同じことをブラウザがやってくれている。

http://blog.onk.ninja/2017/09/21/review_of_restful_api という URL で表される「記事」リソースを HTTP の GET メソッドを使って取得している。

記事の削除も似たようなノリで、リソースを表す http://blog.onk.ninja/2017/09/21/review_of_restful_api という URL に対して DELETE メソッドを使う。

HTTP method とステータスコード

HTTP メソッドと CRUD

CRUD に対応する HTTP メソッドはそれぞれ以下の通り。

HTTP method CRUD
POST Create
GET Read
PUT Update
PATCH Update (部分更新)
DELETE Destroy

ステータスコード

REST は「Web 標準を上手に使う」アーキテクチャなので、ステータスコードも大事にする。

status code 意味
2xx Success
3xx Redirection
4xx Client Error
5xx Server Error

等、よく使うものは決まっているので覚えちゃいましょう。

resource とは

取得する単位。もの。

ものを表すので、名詞になる。

例えば「User と Friend になる」を「User 間の Friendship を生成する」に読み替える等、 慣れがないと見つけるのが難しいものもある。

(この場合、リソースは User と Friendship)

リソースの URI の作り方

まず、「名詞をリソースにして」「リソースの CRUD を HTTP Method で表現する」ということから、 URI には動詞は含まれない ということを意識する。

あとはリソースの本質を表す形になっていれば問題無い。

本質では無いもの、例えば

等が含まれないようになっているとより望ましい。

この辺りは Hypertext Style: Cool URIs don't change. を参照。 日本語訳:クールなURIは変わらない -- Style Guide for Online Hypertext

なお、クールな URI の話が出てきたのは 1998 年で、当時は 検索フォームが POST で実装されているので検索結果の URI が無いとか、 すべて /servlet.do?type=book&id=1 のようにクエリパラメータだけで表現したりとかといった URL が 氾濫していたので、これを「ふつう」にしていこうって動きがあったことを念頭に置いて読むと良いと思う。

今ではあまり異常な URL は見ないので、意識しなくても自然な設計ができるかもしれない。

Rails と REST

Rails v2.0 (2007年12月リリース) で、リリース文に入ってくるぐらい強く RESTful を打ち出している。

We’ve got a slew of improvements to the RESTful lifestyle.

Riding Rails: Rails 2.0: It's done!

v5.1.4 現在では

Rails.application.routes.draw do
  resources :books
end

resources を設定すると、以下の 7 action が定義される。 (PUTPATCH は両方受け付けているが、どちらも update 処理)

   Prefix Verb   URI Pattern               Controller#Action
    books GET    /books(.:format)          books#index
          POST   /books(.:format)          books#create
 new_book GET    /books/new(.:format)      books#new
edit_book GET    /books/:id/edit(.:format) books#edit
     book GET    /books/:id(.:format)      books#show
          PATCH  /books/:id(.:format)      books#update
          PUT    /books/:id(.:format)      books#update
          DELETE /books/:id(.:format)      books#destroy

/books という URL が book の集合 (/books/1, /books/2, ...) を表しているので /books への GET で集合が取得できるし、/books への POST で新しい book を追加できる。 /books/:id に対する GET, PUT/PATCH, DELETE は見たまんまですね。

newedit

ところで

HTTP method CRUD
/books への GET book 一覧の Read
/books への POST book の Create
/books/:id への GET book の Read
/books/:id への PUT/PATCH book の Update
/books/:id への DELETE book の Destroy

はまぁ分かるとして、newedit はいったい何なんだ、動詞は使うなって言ったじゃないかって話になると思うが、 これは「REST の原則からは外れるけれど一般的なアプリケーションには入力フォームを表示するページが必要だよね」って Rails が勝手に作ったデフォルトなので深く気にしないように。 どう考えても無いとツラいものは現実を見て妥協するしかない。原理主義に陥りすぎないこと。

また、例えばいわゆる「コメント欄」では一覧画面に新規作成フォームを入れてしまうのをよく見るし、そうすると new が不要になるので、この 7 つが 必須 というわけでもない。

ちなみに api mode だとこの 2 つは作られなくなります。

nested resources

リソース間に親子関係が存在することがある。

class Group < AR::Base
  has_many :users
end

class User < AR::Base
  belongs_to :group
end

このようなリソースのルーティングは

resources :groups do
  resources :users
end

resources をネストさせることで表現する。

生成される URL は以下の通り。

http://example.com/groups/1/users/2

などがリソースの URL になる。

         Prefix Verb   URI Pattern                                Controller#Action
    group_users GET    /groups/:group_id/users(.:format)          users#index
                POST   /groups/:group_id/users(.:format)          users#create
 new_group_user GET    /groups/:group_id/users/new(.:format)      users#new
edit_group_user GET    /groups/:group_id/users/:id/edit(.:format) users#edit
     group_user GET    /groups/:group_id/users/:id(.:format)      users#show
                PATCH  /groups/:group_id/users/:id(.:format)      users#update
                PUT    /groups/:group_id/users/:id(.:format)      users#update
                DELETE /groups/:group_id/users/:id(.:format)      users#destroy
         groups GET    /groups(.:format)                          groups#index
                POST   /groups(.:format)                          groups#create
      new_group GET    /groups/new(.:format)                      groups#new
     edit_group GET    /groups/:id/edit(.:format)                 groups#edit
          group GET    /groups/:id(.:format)                      groups#show
                PATCH  /groups/:id(.:format)                      groups#update
                PUT    /groups/:id(.:format)                      groups#update
                DELETE /groups/:id(.:format)                      groups#destroy

resource と resources

上では普通の resources を用いて route を定義したが、resource (単数形) というのもある。

Rails.application.routes.draw do
  resource :book
end

この時の routes は以下の 6 action。 index が無くなり、個々の URL から /:id が消えた。

   Prefix Verb   URI Pattern          Controller#Action
     book POST   /book(.:format)      books#create
 new_book GET    /book/new(.:format)  books#new
edit_book GET    /book/edit(.:format) books#edit
          GET    /book(.:format)      books#show
          PATCH  /book(.:format)      books#update
          PUT    /book(.:format)      books#update
          DELETE /book(.:format)      books#destroy

1 つしか存在しないので「一覧」が考えられない場合に使うんだが、 グローバルに 1 つの場合の他に、/users/:id/profile のように has_one の関係になっているものに対しても使う。

resources :users do
  resource :profile
end

こういうものはどう表現する?

csv ダウンロード

取得なので GET です。

クライアントから、欲しいものをサーバに伝えるのに拡張子を使うのはまぁアリ。

GET http://example.com/books.csv

ページネート

取得なので GET です。

「集合」というリソースを取得するときの条件なので、クエリパラメータで表現する。

GET http://example.com/books?page=2

検索

取得なので GET です。

こちらも「集合」というリソースを取得するときの条件なので、クエリパラメータで表現する。

GET http://example.com/books?q=hoge

検索は頻出するので、分かりやすさ優先で

GET http://example.com/books/search?q=hoge

を許しても良いかもしれない。議論の余地があるところだと思う。

確認画面

URL として用意せずにクライアント側で処理するのが理想。

どうしてもやるなら draft っていう状態を持たせることを考える。

確認画面問題とリソースモデリング - masakiのはてなダイアリー http://d.hatena.ne.jp/ikasam_a/20080305/1204733745

確認画面も頻出するので、分かりやすさ優先で

POST http://example.com/books/confirm

を許しても良いかもしれない。悩ましい。

公開フラグを立てる

確認画面に draft フラグを用意した時と同じ。

更新なので PATCH です。

PATCH http://example.com/books/:id

draft=false

別解として、「公開」というリソースを POST / DELETE することで 公開状態を変更するというのもアリ。

POST http://example.com/books/:id/publication

参考書

圧倒的にこの 2 冊。特に後者は web 系企業だとだいたい入社前に必読書として渡してると思う。

前後リンク