RESTfull APIの構築を計画していますが、頭の中でいくつかの問題を引き起こしているいくつかのアーキテクチャの質問があります。クライアントにバックエンドビジネスロジックを追加することは避けたいオプションです。ビジネスロジックが急速に変化する可能性がある場合、複数のクライアントプラットフォームの更新をリアルタイムで維持するのは難しいためです。
リソース(api/article)として記事があるとしましょう。公開、非公開、アクティブ化、非アクティブ化などのアクションをどのように実装すればよいのでしょうか。
1)リモートの場所へのプッシュや複数のプロパティの変更など、多くのバックエンドロジックが発生する可能性があるため、api/article/{id}/{action}を使用する必要があります。おそらくここで最も難しいのは、更新のためにすべての記事データをAPIに送り返す必要があり、マルチユーザー作業を実装できなかったことです。たとえば、編集者は5秒前のデータを送信し、他の一部のジャーナリストが2秒前に行った修正を上書きすることができます。記事の公開はコンテンツの更新にまったく関係がないため、クライアントにこれを説明する方法はありません。
2)新しいリソースの作成もオプションapi/article- {action}/idにすることができますが、返されるリソースはarticle- {action}ではなく、記事が適切かどうかは不明です。また、サーバー側のコード記事のクラスでは、両方のリソースで実際の作業を処理しており、これがRESTfullの考え方に反するかどうかはわかりません
どんな提案も歓迎します。
私は記述された実践 ここ が役立つと思います:
CRUD操作の世界に適合しないアクションはどうですか?
これは、物事が曖昧になる可能性がある場所です。いくつかのアプローチがあります:
- リソースのフィールドのように見えるようにアクションを再構成します。これは、アクションがパラメーターを取らない場合に機能します。たとえば、activateアクションをブール
activated
フィールドにマップし、PATCHを介してリソースに更新できます。- RESTfulの原則を持つサブリソースのように扱います。たとえば、GitHubのAPIを使用すると、 スターギスト with
PUT /gists/:id/star
および nstar withDELETE /gists/:id/star
を使用できます。- アクションを賢明なRESTful構造にマップする方法がまったくない場合があります。たとえば、マルチリソース検索は、特定のリソースのエンドポイントに適用しても意味がありません。この場合、
/search
はリソースではありませんが、最も意味があります。これは問題ありません。APIコンシューマの観点から正しいことを行い、混乱を避けるために明確に文書化されていることを確認してください。
説明する「公開」アクションのように、サーバー側で大きな状態と動作の変化をもたらす操作は、RESTで明示的にモデル化することが困難です。私がよく目にする解決策は、そのような複雑な動作をデータを介して暗黙的に駆動することです。
REST APIはオンライン販売者から公開されています。注文は複雑な操作です。複数の製品がパッケージ化されて出荷され、アカウントに請求が行われ、領収書が届きます。期間限定でご注文をキャンセルできます。もちろん、返金のために製品を返送できる全額返金保証があります。
このようなAPIでは、複雑な購入操作の代わりに、新しいリソースである注文書を作成できる場合があります。最初に、商品の追加や削除、配送先住所の変更、別の支払いオプションの選択、注文のキャンセルなど、必要に応じて変更を加えることができます。まだ何も購入しておらず、サーバー上のデータを操作しているだけなので、これらすべてを実行できます。
購入注文が完了して猶予期間が経過すると、サーバーは注文をロックしてそれ以上の変更を防止します。この時点でのみ、複雑な一連の操作が開始されますが、直接制御することはできません。以前に発注書に入力したデータを介して間接的に制御するだけです。
あなたの説明に基づいて、「公開」はこの方法で実装できます。操作を公開する代わりに、レビューしたドラフトのコピーを配置し、/ publishの下に新しいリソースとして公開します。これにより、公開操作自体が数時間後に完了しても、ドラフトに対する後続の更新が公開されないことが保証されます。
すべての記事データを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
リクエストに「バージョン」が添付されていないため、サーバーはリソースを作成しませんでした(/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
を使用してリクエストサイクルを節約することもできますが、バージョン番号は他のマジックによってリクエストにエンコードする必要があります。要求/応答サイクルを最小限に抑えるために、実際のAPIで推奨されます。
ドキュメントのコンテンツではなく、一時的な状態を扱う別の例を次に示します。 (私はバージョニングを見つけます-一般的に、各バージョンは新しいリソースになる可能性があります-一種の簡単な問題です。)
RESTを介して、マシン上で実行されているサービスを公開して、停止、開始、再起動などができるようにしたいとします。
ここで最もRESTfulなアプローチは何ですか? POST/service?command = restart、たとえば??またはPOST/service/state with a body of 'running'?
ここでベストプラクティスを体系化し、RESTがこのタイプの状況に適切なアプローチであるかどうかを示します。
次に、自分の状態には影響せず、副作用を引き起こすサービスからアクションを実行したいとします。たとえば、電話時に作成されたレポートを多数のメールアドレスに送信するメーラーサービス。
GET/reportは、自分でレポートのコピーを取得する方法の1つかもしれません。しかし、上で言ったように、電子メール送信などのさらなるアクションをサーバー側にプッシュしたい場合はどうでしょうか。または、データベースへの書き込み。
これらのケースはリソースとアクションの分割に関連して踊り、REST指向の方法でそれらを処理する方法を私は見ますが、率直に言って、そうすることは少しハックのように感じます。おそらく重要な問題は、REST APIが一般的に副作用をサポートする必要があるかどうかです。
RESTはデータ指向であるため、リソースはアクションではなく「もの」として最適に機能します。 httpメソッドの暗黙のセマンティクス。 GET、PUT、DELETEなどは、オリエンテーションを強化するのに役立ちます。 POSTもちろん、例外です。
リソースは、データの混合である場合があります。記事の内容;とメタデータ、すなわち。公開、ロック、改訂。データをスライスする方法は他にもたくさんありますが、最適なものがある場合は、それを決定するために、最初にデータフローがどのようになるかを確認する必要があります。たとえば、TokenMacGuyが示唆するように、記事の下でリビジョンを独自のリソースにする必要がある場合があります。
実装に関しては、おそらくTockenMacGuyが示唆するようなことをするでしょう。また、「ロック」や「公開」のように、リビジョンではなく記事にメタデータフィールドを追加します。
記事の状態を直接操作しているとは考えないでください。代わりに、記事の作成を要求する変更要求を入力します。
新しい変更管理リソース(POST)を作成することで、変更管理をモデル化できます。多くの利点があります。たとえば、記事を変更管理の一部として公開する必要がある将来の日時を指定し、それがどのように実装されるかをサーバーに任せることができます。
公開が瞬間的なプロセスではない場合、クライアントに戻る前に、公開が完了するのを待つ必要はありません。変更管理が作成されたことを確認し、変更管理IDを返します。次に、その変更管理に対応するURLを使用して、変更管理のステータスを共有できます。
私にとって重要な洞察は、この変更管理のメタファーはオブジェクト指向プログラミングを説明するためのもう1つの方法にすぎないことを認識することでした。リソースではなく、オブジェクトと呼びます。変更管理の代わりに、メッセージと呼びます。 OO=でAからBにメッセージを送信する1つの方法は、AにBのメソッドを呼び出させることです。特にAとBが異なるコンピューターにある場合、それを行う別の方法は、 Aは新しいオブジェクトMを作成し、Bに送信します。RESTは単にそのプロセスを形式化します。
私があなたを正しく理解していれば、あなたが持っているのは、技術的な問題というよりも「ビジネスルール」の決定に関する問題だと思います。
記事を上書きできるという事実は、上級ユーザーがジュニアユーザーのバージョンを上書きできる承認レベルを導入することで解決できます。また、記事の状態をキャプチャするためのバージョンと列を導入することで(例:「開発中」、「最終版」)など)、これを克服できます。また、提出時間とバージョン番号の組み合わせによって、ユーザーに特定のバージョンを選択する権限を与えることもできます。
上記のすべてのケースで、サービスは設定したビジネスルールを実装する必要があります。したがって、ユーザーID、記事、バージョン、アクション(バージョンはオプションですが、これはビジネスルールによって異なります)でサービスを呼び出すことができます。