RESTfulな設計とCRUDはちょっと相性悪いという話
http://www.infoq.com/jp/news/2009/08/CRUDREST
上記URLを読んで自分なりに例題を考えてみる。(todo:あとで状態遷移図を追加)
RestBucks cafe
完全にオンライン化されていてWebAPIで注文できるというすごいカフェを想定します。(この手の例にStarbucksを使うのはGregor Hopeさん以来の伝統らしいです)
客側から見たユースケースはこんな感じ
- 客はレジのサービスを呼び出して、注文を入力して支払い
- 自席で注文状況をチェック、出来上がっていたら取りに行く。
注文というエンティティと、[注文編集] [支払い] [受け取り] という(アプリケーション)状態によって上手く表現できそうです。
(RESTfulだけど)CRUDな設計
CURDな設計では、リソースをURLにマッピングします。それに対してCRUDするというイメージです。*1
- /order にPOST して注文を作成する
- /order/1234 にPUT :トッピングを追加
- /order/1234 にPUT:[支払い]に状態変更
- /order/1234 にPUT:[受取]に状態変更
- /order/1234 をGETして状況確認
RESTを意識するかぎり、状態に関する情報をセッション等に保持するのは避けたいので、注文エンティティに持たせることになるでしょう(アプリケーション状態からリソース状態に昇格させる)。そしてPOSTによって注文リソースを編集することによって、状態遷移を引き起こします。
この状態遷移のルールは、アプリケーションの中に隠されてます。
- 支払いをすると受け取り可能になる
- 支払いをする前に受取に状態変更しようとするとエラー
- 支払い済みものに、トッピングを追加しようとするとエラー
- ...
この例では、実はクライアント側にある種の前提を強いています。つまり状態遷移のモデルをクライアント開発側も把握している必要があります。
HATEOASな設計
HATEOAS は Hypermedia as the Engine of Application State の略です。状態マシンをハイパーメディアで表現するという意味ですね。
HATEOASな設計では、状態*2をURLにマッピングし、アクションをHTTPメソッドに、状態遷移をリンクにマッピングします。
[注文前の状態] /order
- POSTでcreate : 次の状態を返却[/order/1234]
[注文] /order/1234
- GETで注文内容確認
- PUTで注文編集 : 次の状態を返却[/order/payment/1234]
[支払い] /order/payment/1234
- PUTで支払い確定 : 次の状態を返却[/order/receive/1234]
[受け取り] /order/receive/1234
- GETしてチェック
状態遷移のルールは、URLとハイパーリンク、HTTPメソッドの制限によって表現できます
- 注文編集をした後に遷移する次の状態は、支払いの状態を意味するハイパーリンク(/order/payment/1234)を返却データに含めることで表現。
- 支払い前に受け取りできないのは、注文作成後にいきなり /order/recieve/1234 をGETすると404になることで表現。
- 支払い後に注文できないのは、支払い後に order/1234 のPUTが制限されることによって表現。(OPTIONSで確認可能)
HATEOASな設計ではクライアント側に必要な前提条件を減らすことができます。状態マシンが変更されても、それはURLとハイパーリンクの変更で表現されます。たとえば、[注文]と[支払い]の間に[スピードくじ抽選]という状態ができても、注文後に遷移する先が変更されるだけです。(完全に前提知識無しというのは無理でしょうが)
ただし状態に対するアクションが4つしか定義できない、というプロトコル上の制約があります。その場合は、状態を細かく分割する必要があるかも。([注文]と[注文追加]を分けるとか)*3
そういえば、id:t-wada さんがやっていた RESTfulBuri もこんな感じの実装少し似た感じの設計だったと思います。さすが!!