RESTFul:状態変更アクション


59

私はRESTfull APIを構築する予定ですが、私の頭の中にいくつかの問題を引き起こしているいくつかのアーキテクチャ上の質問があります。クライアントにバックエンドビジネスロジックを追加することは、ビジネスロジックが急速に変化する可能性があるため、複数のクライアントプラットフォームの更新をリアルタイムで維持するのが難しいため、回避したいオプションです。

リソースとして記事(api / article)があるとしましょう。発行、非公開、アクティブ化、非アクティブ化などのアクションをどのように実装する必要がありますか?

1)api / article / {id} / {action}を使用する必要があります。リモートロケーションへのプッシュや複数のプロパティの変更など、多くのバックエンドロジックがそこで発生する可能性があるためです。おそらくここで最も難しいことは、更新のためにすべての記事データをAPIに送り返す必要があり、マルチユーザーの作業を実装できなかったことです。たとえば、エディターは5秒前のデータを送信し、他のジャーナリストが2秒前に行った修正を上書きすることができます。また、記事を公開している人はコンテンツの更新とはまったく関係がないため、これをクライアントに説明する方法はありません。

2)新しいリソースの作成もオプションapi / article- {action} / idですが、返されるリソースはarticle- {action}ではなく、これが適切かどうかわからない記事になります。また、サーバー側のコード記事クラスでは、両方のリソースで実際の作業を処理していますが、これがRESTfull思考に反するかどうかはわかりません

どんな提案も歓迎します。


「アクション」をRESTful URIの一部にすることは完全に合法です-アクション/アルゴリズムが実行されることを述べている場合。何が問題なのapi/article?action=publishですか?クエリパラメータは、リソースの状態が、言及した「アルゴリズム」(またはアクション)に依存するような場合を対象としています。例:api/articles?sort=asc有効
博士号

1
私はあなたがチェックアウトをお勧めこれも、とあなたを刺激する書き込みアップ、より多くの RESTfulなソリューションを。
ベンジョー

?私は、API /記事を参照して、アクション=パブリッシュ問題の一つは、私はこれを行うことを好むでしょうがRESTfullアプリケーションでの公開のためのすべての商品データを送信するべきであるとされています。API /記事/ 4545を/追加何もせずに、パブリッシュ/
ミロSvrtan

2
この問題に関する優れたリソースなど:REST APIデザイン-リソースモデリング
イザキ

@PhD:プロトコルでは完全に合法ですが、URIは通常動詞ではなく名詞である必要があります。URIに動詞があることは、通常、RESTの設計が悪いことを示しています。
ライライアン

回答:


49

ここで説明さているプラクティスが役立つことがわかりました。

CRUD操作の世界に適合しないアクションはどうですか?

これは、物事が曖昧になる可能性がある場所です。多くのアプローチがあります:

  1. リソースのフィールドのように表示されるようにアクションを再構築します。これは、アクションがパラメーターを受け取らない場合に機能します。たとえば、アクティブ化 アクションはブールactivatedフィールドにマップされ、PATCHを介してリソースに更新されます。
  2. RESTful原則を備えたサブリソースのように扱います。例えば、GitHubでのAPIは、あなたがすることができます要旨を主演PUT /gists/:id/starしてスターを外すDELETE /gists/:id/star
  3. アクションを理にかなったRESTful構造にマップする方法がない場合があります。たとえば、マルチリソース検索は、特定のリソースのエンドポイントに適用するのは実際には意味がありません。この場合、 /searchリソースではない場合でも最も意味があります。これは問題ありません。APIコンシューマーの観点から正しいことを行い、混乱を避けるために明確に文書化してください。

私はアプローチ2に投票します。API呼び出し元にとっては不器用な人工リソースのように見えるかもしれませんが、実際にはサーバー上で何が起こっているのかを知りません。POSTを呼び出し/article/123/deactivationsて記事123の新しい非アクティブ化リクエストを作成すると、サーバーはリクエストされたリソースを非アクティブ化するだけでなく、非アクティブ化リクエストを実際に保存して後でステータスを取得できるようにします。
-JustAMartin

2
なぜPUT /gists/:id/star ありませんかPOST /gists/:id/star
フィリップバルトゥージ

9
@FilipBartuzi PUTはべき等であるため、つまり、同じパラメーターで何度アクションを実行しても、結果は常に同じです(たとえば、ライトをオフからオンにすると、変化します。再びオンになり、何も変化しません-既にオンになっています)。POSTはi等ではありません。つまり、同じパラメーターを使用してアクションを実行するたびに、アクションの結果は異なります(たとえば、誰かに手紙を送った場合、その人は手紙を受け取ります。同じ人、彼らは今2文字を持っています)。
ラファエル

9

説明する「公開」アクションなど、サーバー側で大きな状態と動作の変更をもたらす操作は、RESTで明示的にモデル化することは困難です。私がよく見かける解決策は、データを介して暗黙的にそのような複雑な動作を駆動することです。

オンライン販売者によって公開されたREST APIを介して商品を注文することを検討してください。順序付けは複雑な操作です。複数の製品がパッケージ化されて出荷され、アカウントに課金され、領収書が発行されます。限られた時間で注文をキャンセルすることができ、もちろん返金のために製品を返送できる完全な返金保証があります。

複雑な購入操作の代わりに、このようなAPIを使用すると、新しいリソースである注文書を作成できます。最初に、製品の追加や削除、配送先住所の変更、別の支払いオプションの選択、注文のキャンセルなど、必要に応じて変更を加えることができます。まだ何も購入していないため、これらすべてを実行できます。サーバー上のデータを操作するだけです。

購入注文が完了して猶予期間が過ぎると、サーバーは注文をロックして、それ以上の変更を防ぎます。この時点でのみ複雑な一連の操作が開始されますが、直接制御することはできません。以前に発注書に入れたデータを介して間接的に制御するだけです。

あなたの説明に基づいて、この方法で「公開」を実装できます。操作を公開する代わりに、レビューしたドラフトのコピーを/ publishの下に新しいリソースとして公開します。これにより、公開操作自体が数時間後に完了しても、ドラフトに対する後続の更新が公開されないことが保証されます。


リソース全体を未公開の記事からドラフトに変更するという考えは、このケースにぴったりですが、一般的なリソースに存在する他のすべてのアクションには合いません。RESTはそれを処理することさえ想定されていますか?たぶん私はそれを乱用しており、CRUDとしてのみ使用する必要があります.SQLクエリのように、ロジック自体がクエリ自体の内部にあることを期待しないでください。
ミロSvrtan

なぜ私はこれをすべて求めているのですか?しばらく前からウェブアプリがマルチプラットフォーム化され始めており、iOS、Android、ウェブ、デスクトップなどのプラットフォームでビジネスロジックを更新することはほとんど不可能になっているため、サーバー上で多くのビジネスロジックを保持したいと思います。 BLの一部を変更するときに、下位互換性の問題をすべて回避したいと思います。
ミロSvrtan

2
RESTはビジネスロジックをうまく処理できると思いますが、RESTを意識せずに作成された既存のビジネスロジックを公開するのには適していません。これが、MicrosoftやSAPなどの多くの企業がCRUD操作でデータのみを公開することが多い理由です。Open data Protocolを見て、どのように機能するかを確認してください。
フェレンツ・ミハーイ

7

更新のためにすべての記事データをAPIに送信する必要があり、マルチユーザー作業を実装できませんでした。たとえば、エディターは5秒前のデータを送信し、他のジャーナリストが2秒前に行った修正を上書きすることができます。また、記事を公開している人はコンテンツの更新とはまったく関係がないため、これをクライアントに説明する方法はありません。

この種のことは、あなたが何をするにしてもチャレンジです。それは分散ソース管理(mercurial、gitなど)と非常に似た問題であり、HTTP / ReSTで綴られたソリューションは少し似ています。

AliceとBobの2人のユーザーがいて、両方ともで作業しているとし/articles/lunchます。(わかりやすくするため、応答は太字で示しています)

最初に、アリスは記事を作成します。

PUT /articles/lunch HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

301 Moved Permanently
Location: /articles/lunch/1 

リクエストに「バージョン」が添付されていなかったため、サーバーはリソースを作成しませんでした(IDを仮定します/articles/{id}/{version}。作成を実行するために、アリスは作成する記事/バージョンのURLにリダイレクトされました。アリスのユーザーエージェントは新しいアドレスでリクエストを再適用します。

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

201 Created

そして今、記事が作成されました。次に、ボブは記事を見ます:

GET /articles/lunch HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

301 Moved Permanently
Location: /articles/lunch/1 

ボブはそこを見ます:

GET /articles/lunch/1 HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

200 Ok
Content-Type: text/plain

Hey Bob, what do you want for lunch today?

彼は自分の変更を追加することにしました。

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

301 Moved Permanently
Location: /articles/lunch/2

アリスと同様に、ボブは新しいバージョンを作成する場所にリダイレクトされます。

PUT /articles/lunch/2 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

201 Created

最後に、アリスは自分の記事に追加したいと判断します。

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?
I was thinking about getting Sushi.

409 Conflict
Location: /articles/lunch/3
Content-Type: text/diff

---/articles/lunch/2
+++/articles/lunch/3
@@ 1,2 1,2 @@
 Hey Bob, what do you want for lunch today?
-Does pizza sound good to you, Alice?
+I was thinking about getting Sushi.

通常のようにリダイレクトされる代わりに、異なるステータスコードがクライアントに返されます409。これは、アリスに、彼女がブランチしようとしたバージョンがすでにブランチされていることを伝えます。とにかく(Locationヘッダーで示されているように)新しいリソースが作成され、2つの間の違いが応答本文に含まれました。アリスは、今行ったリクエストを何らかの方法でマージする必要があることを知っています。


このリダイレクトはすべて、のセマンティクスに関連してPUTいます。これは、要求行が要求する場所に新しいリソースを作成する必要があります。これはPOST代わりに使用してリクエストサイクルを節約することもできますが、バージョン番号は他の魔法によってリクエストでエンコードする必要があります。要求/応答サイクルを最小限に抑えるため。


1
ここでVersoningは問題ではなく、記事をパブリッシングアクションのリソースとして使用する場合に起こりうる問題の例として述べただけです
ミロSvrtan

3

ドキュメントのコンテンツではなく、一時的な状態を扱う別の例を次に示します。(バージョニングを見つけます-一般的に、各バージョンは新しいリソースになりうることを考えると、一種の簡単な問題です。)

REST経由でマシン上で実行されているサービスを公開して、停止、開始、再起動などを行えるようにしたいとします。

ここで最もRESTfulなアプローチは何ですか?POST / service?command = restart、たとえば?または、たとえば「実行中」の本文で/ service / stateをPOSTしますか?

ここでベストプラクティスを体系化し、RESTがこの種の状況に適切なアプローチであるかどうかを体系化しておくと便利です。

第二に、自身の状態に影響を与えず、副作用を引き起こすサービスから何らかのアクションを引き起こしたいと仮定しましょう。たとえば、呼び出し時に作成されたレポートを多数の電子メールアドレスに送信するメーラーサービス。

GET / reportは、レポートのコピーを自分で取得する方法かもしれません。上記のようにメール送信などのアクションをサーバー側にプッシュしたい場合はどうでしょうか。または、データベースへの書き込み。

これらのケースはリソースとアクションの分割を中心に舞い、私はそれらをREST指向の方法で処理する方法を見ていますが、率直に言って、それを行うのはちょっとしたハックのように感じます。おそらく重要な質問は、REST APIが一般的な副作用をサポートする必要があるかどうかです。


2

RESTはデータ指向であるため、リソースはアクションではなく「もの」として最適に機能します。httpメソッドの暗黙的なセマンティクス。GET、PUT、DELETEなどは方向を強化するのに役立ちます。もちろん、POSTは例外です。

リソースはデータの混合である場合があります。記事の内容; およびメタデータ。公開、ロック、改訂。データをスライスする方法は他にもたくさんありますが、最適なもの(ある場合)を決定するには、最初にデータフローがどのように見えるかを調べる必要があります。たとえば、TokenMacGuyが示唆しているように、改訂は記事の下で独自のリソースである必要があるかもしれません。

実装に関しては、おそらくTockenMacGuyが提案するようなことをするでしょう。また、「ロック済み」や「公開済み」など、改訂ではなく記事にメタデータフィールドを追加します。


1

記事の状態を直接操作するものと考えないでください。代わりに、記事の作成を要求する変更命令を入れています。

新しい変更要求リソース(POST)の作成として、変更要求の入力をモデル化できます。多くの利点があります。たとえば、変更命令の一部として記事を発行する将来の日付と時刻を指定し、それがどのように実装されるかをサーバーに心配させることができます。

公開が瞬間的なプロセスではない場合、クライアントに戻る前に公開が完了するのを待つ必要はありません。変更要求が作成されたことを確認し、変更要求IDを返します。その後、その変更要求に対応するURLを使用して、変更要求のステータスを共有できます。

私にとって重要な洞察は、この変更命令のメタファーがオブジェクト指向プログラミングを記述するための別の方法であるということを認識することでした。リソースの代わりに、オブジェクトを呼び出します。変更要求の代わりに、それらをメッセージと呼びます。OOでAからBにメッセージを送信する1つの方法は、AにBのメソッドを呼び出すことです。特に、AとBが異なるコンピューターにある場合、Aに新しいオブジェクトMを作成させることです。 Bに送信します。RESTは単にそのプロセスを形式化します。


実際、これはオブジェクト指向よりもアクターモデルに近いと考えています。RESTリクエストをメッセージ(それがメッセージ)であると考えると、状態を管理するためのイベントソーシングに非常にきちんと合わせられます。RESTは相互作用をCQRSラインに沿ってきれいに分割します。GETメッセージは、クエリ、POST、PUT、PATCH、DELETEがコマンド領域に分類されます。
-WillD

0

私があなたを正しく理解していれば、あなたが持っているのは技術的な問題というよりも「ビジネスルール」の決定の問題だと思います。

記事を上書きできるという事実は、上級ユーザーが下位ユーザーのバージョンを上書きできる認証レベルを導入することで解決できます。また、記事の状態をキャプチャするためのバージョンと列を導入することで解決できます(例:「開発中」、「最終」など)、これを克服できます。また、ユーザーは、送信時間とバージョン番号の組み合わせによって、特定のバージョンを選択する機能を提供できます。

上記のすべての場合において、サービスは設定したビジネスルールを実装する必要があります。したがって、ユーザーID、記事、バージョン、アクションのパラメーターを使用してサービスを呼び出すことができます(バージョンはオプションですが、これもビジネスルールによって異なります)。


これはビジネスルールであるが、厳密には技術的なルールであることを信じていません。バージョンを追加するという考え方は、ルールのオーバーライドに役立つものですが、コンテンツの更新とコンテンツの公開が関連するアクションではないという状況を解決することはできません。
ミロSvrtan
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.