QA@IT

REST API の設計について教えて下さい

9593 PV

こんばんは。
最近はじめて Web アプリケーションを作ることになったのですが, REST API の設計について少しお聞きしたいことがあり投稿しました。

アプリケーションの概要

クライアントは Web ブラウザです。フロントエンドは Backbone.js で作成し, バックエンドの REST API を用いてデータの取得/更新を行います。

REST API の設計

ここではマスタ機能について考えます。例えばあるマスタ User が存在するとします。マスタには以下の機能が存在し, それぞれ右の URL を通じて提供されます(Backbone.js でルーティング)。

  • 一覧画面:/users
  • 新規画面:/users/new
  • 詳細画面:/users/1
  • 編集画面:/users/1/edit

ここでバックエンドの API はそれぞれこんな感じにしています。

  • 一覧取得:GET /api/v1/users
  • 新規作成:POST /api/v1/users
  • 単体取得:GET /api/v1/users/1
  • 単体更新:PUT /api/v1/users/1
  • 単体削除:DELETE /api/v1/users/1

このあたりまではそれほど問題ないかと思っています。問題は次のような機能がある場合です。

  1. 一括削除
  2. (修正内容などの)承認
  3. (論理削除されたデータの)復活
  4. アカウントロック

このような操作は一般的にどのように API を設計すると良いのでしょうか?以下に自分の意見を記載します。

1. 一括削除

これについては専用のリソースを設けるしかないかなと思っています。なので以下のような API で良いかなと思うのですが, POST で良いのかがちょっと分かりません……。また, リクエスト/レスポンスの内容についても少々悩んでいます(今のところリクエストは削除対象の ID のリストを持つ JSON にしようかなと思っています)。

POST /api/v1/users/purge

2. 承認

承認する際は, サーバーサイドである程度の複雑な業務ロジックが実行され, リソースの更新や場合によっては新規リソースの作成が行われることがあります(例えば承認前データを退避し, 別レコードを新規作成する等)。この場合は恐らく専用のリソースを作れば良いのかと思うのですが, あんまり自信がありません(POST で良いのでしょうか……?)。今のところは以下のような API を提供し, 承認後の User がレスポンスとして返ってくるようなイメージで良いのかなと思っています(リクエストのエンティティボディは特に無くても良さそう?)。

POST /api/v1/users/1/authorize

3. 復活

復活の場合は, 例えば User.deletedtrue から false に更新される程度だと仮定してみます。この場合はサーバーサイドでは特に複雑な業務ロジックが走るわけでもなく, 実態としては PUT /api/v1/users/1 を呼び出すのと同様の処理で済みます。このような場合はクライアントで User を更新し, 単に PUT /api/v1/users/1 に更新リクエストを飛ばしても良いのですが, 仮に承認機能も一緒に存在するのであれば, 何だか平仄が合っていないのが気になります。結局これも専用のリソースを作ることに……。

POST /api/v1/users/1/restore

4. アカウントロック

これも 3. の復活と同じなので, 仮に User.lockStatus を更新するだけだとすると専用のリソースを作るべきか悩んでしまいます……。感覚的にはやっぱりここも専用のリソースを作ってこんな感じに……。

POST /api/v1/users/1/lock

以上です。自分としては(些か冗長ですが)業務機能毎に(その実態が単なる更新処理と同じであったとしても)API を切ってしまうのが整合性も取れて良いのかなと思うのですが, 上記の機能・API の設計について, 皆さんのご意見を頂けると幸いです(Rails 界隈の方が詳しそう & 事例豊富そうなので Rails タグも付けてみましたが, 不適切なら指摘頂ければ削除します)。

回答

そのWebアプリケーションが「フロントエンド」と「バックエンド」の2つのコンポーネントに分かれる構成になっているなら、「一括削除」「承認」「復活」「アカウントロック」の4つの機能を、どちらのコンポーネントに実装すべきなのかよく考えた方がいいと思います。

例えば10個の項目を一括削除する場合は、単純に考えればDELETEを10回繰り返すだけです。
その10個の項目が一つのパスの配下にあるなら、その親パスを1回DELETEするだけで済むこともありますし、ある条件にマッチした項目全てということなら?q=hogeのようにクエリーを使うこともできます。

編集 履歴 (0)
  • そうですね。そういう意味では上記の例について言うと、各種業務機能をバックエンド側で提供する方が平仄が取れて良いのかなと思ったのでそのようにしているのですが、皆さんならどうするのかご意見を伺いたいと思って投稿させて頂きました。3 / 4 の例で言うとフロントエンド側で実装することも可能ですが、もしフロントエンドに寄せるならどのような観点でフロントエンドに寄せると判断するのかなと。 -
  • 今回は、バックエンドがRESTであるという前提条件があるのですから、それ以外の部分がフロントエンドなのでは?例えば、リソースの状態変更はPUTです。それをアカウントロックに見せかけるのはフロントエンドの仕事になるのでは? -
  • なるほど stripe さんはクライアントに寄せて PUT にするということですね。ちなみにこのケースで言うと「承認」のように複雑な業務ロジックがあるようなケースもやはりクライアントで実装して PUT するイメージなのでしょうか? -
  • ん?クライアント??ここで言う「フロントエンド」とは「クライアント」のことだったんですか? -
  • あぁ、すみません言葉の使い方が悪かったでしょうか。stripe さんの仰る「フロントエンド」と「クライアント」の意味が分かりませんが、質問にある通りクライアントは Web ブラウザです。Web ブラウザで動作している JavaScript アプリケーションのことをフロントエンドと呼ぶと理解出来るでしょうか。 -
  • あぁ、そういうことですか。サーバーサイドがフロントエンドとバックエンドに分かれているのかと思いました。まあ、フロントエンドがサーバーでもクライアントでもアプリケーションとしての構成は同じですが、セキュリティの設計が変わってくるのかもしれませんね。 -
  • 承認処理の内容がいまいちよく分かりませんが。例えば、あるユーザーが「未承認」フラグの付いたリソースをPUTし、別のユーザー(決裁者)がフラグを「承認」にしてPUTし直す、という形にはならないでしょうか? -
  • ここでの承認はあくまで例ですが、質問の通りかなり複雑な業務ロジックがあると想定しています。レコードのステータスが複雑に遷移したり、承認前後のレコードが作成されて登録されたり、監査ログが永続化されたりといった具合です。このような業務ロジックはやはりサーバーサイドで永続化することになるのかなと思い、そうすると平仄をとるためにアカウントロック等もサーバーサイドに寄せるべきなのかなと考えていました……。 -
  • えーと、ですから、「承認処理は複雑である」という説明だけだと内容がよく分からないのでこれ以上答えられません。逆にいうと、「承認処理は複雑である」と大雑把な考えかたをしているからうまく設計できないのでは?もっと処理内容の詳細を整理した方がよいと思います。 -
  • stripe さんの質問に答えると「PUTし直す、という形にはならない」ケースを考えています。勿論詳細な内容はあるのですが、それを書くことは出来ないのでここでは例として「承認」のように「クライアントで実装するのが困難と思われるような処理」と「ロック」のように「クライアントでも実装出来そうな処理」が共にある場合、一般的には皆さんはどのように設計するんだろうというのが知りたいところでした。 -
  • ちょっと質問の仕方が悪かったですかね……。ここ日本語サイトなので皆さんならどうするんだろうという意見が聞けるかなと思ったのですが、場所を変えます……。ありがとうございました! -

任意の複数項目の削除を行いたい場合、

まずPOSTでその複数アイテムを意味するリソースに対してIDを作成し、
そのあとで DELETEするという手もあるみたいです。

POST /api/v1/users/selections
→ パラメータでID群を受け取り サーバーがuniq_idを作成し、クライアントはuniq_idを手に入れる。

DELETE /api/v1/users/selections/{uniq_id}

とするようです。
DELETEの後 uniq_idはどうするんだろ。
冪等性のために持ち続けるというのもイマイチな気も。

http://stackoverflow.com/a/2445682/2513010

編集 履歴 (0)
  • 回答ありがとうございます。お~なるほど一連のトランザクションをリソースと考えて、選択したレコードの ID を POST したらサーバー側で削除トランザクションのリソース作成 → コミットするという感じですね。まぁしかし少々迂遠過ぎる感じもしますね……。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。