RESTfulサービスでの部分更新のベストプラクティス


208

私は顧客管理システム用のRESTfulサービスを作成しており、レコードを部分的に更新するためのベストプラクティスを見つけようとしています。たとえば、呼び出し元がGETリクエストでレコード全体を読み取れるようにしたいとします。ただし、更新の場合、ステータスをENABLEDからDISABLEDに変更するなど、レコードに対する特定の操作のみが許可されます。(これよりも複雑なシナリオがあります)

セキュリティ上の理由から、更新されたフィールドのみを含むレコード全体を発信者に送信してほしくありません(また、やり過ぎのように感じます)。

URIを構築するための推奨される方法はありますか?RESTブックを読むとき、RPCスタイルの呼び出しは不快に思われます。

次の呼び出しがID 123の顧客の完全な顧客レコードを返す場合

GET /customer/123
<customer>
    {lots of attributes}
    <status>ENABLED</status>
    {even more attributes}
</customer>

ステータスを更新するにはどうすればよいですか?

POST /customer/123/status
<status>DISABLED</status>

POST /customer/123/changeStatus
DISABLED

...

更新:質問を補足します。「ビジネスロジックコール」をREST APIに組み込むにはどうすればよいですか?これを行うための合意された方法はありますか?すべてのメソッドが本質的にCRUDであるとは限りません。いくつかは'のような、より複雑なsendEmailToCustomer(123) '、 ' mergeCustomers(123、456) '、 ' countCustomers() '

POST /customer/123?cmd=sendEmail

POST /cmd/sendEmail?customerId=123

GET /customer/count 

3
「ビジネスロジックコール」についての質問に答えるには、POSTロイフィールディング自身からの投稿です:roy.gbiv.com/untangled/2009/it-is-okay-to-use-post基本的な考え方は、オペレーションの使用に理想的に適したメソッド(GETまたはなどPUT)ではありませんPOST
rojoca

これは結局私がやったことです。GET、PUT、DELETEを使用して、既知のリソースを取得および更新するためのREST呼び出しを行います。新しいリソースを追加するためのPOSTと、ビジネスロジック呼び出しの説明URLを含むPOST。
magiconair 2011年

何を決定しても、その操作がGET応答の一部でない場合、RESTfulサービスはありません。私はここでそれを見ていないよ
MStodd

回答:


69

基本的に2つのオプションがあります。

  1. 使用PATCH(ただし、正確に何が起こるかを指定する独自のメディアタイプを定義する必要があることに注意してください)

  2. POSTサブリソースに使用し、303を参照してください。メインリソースを指すLocationヘッダーを持つその他を参照してください。303の目的は、クライアントに次のことを伝えることです。POST / 303は、いくつかのメインリソースの状態を構築するためにリソースに繰り返し追加することを目的としており、部分的な更新に最適です。


OK、POST / 303は私には理にかなっています。PATCHとMERGE有効なHTTP動詞のリストに見つからなかったため、さらにテストが必要になります。システムが顧客123に電子メールを送信するようにしたい場合、どのようにURIを構築しますか オブジェクトの状態をまったく変更しない、純粋なRPCメソッド呼び出しのようなもの。これを行うRESTfulな方法は何ですか?
magiconair 2010年

メールURIの質問が理解できません。メールを送信するためにPOSTできるゲートウェイを実装しますか、それともmailto:customer.123@service.orgを探していますか?
Jan Algermissen、2010年

15
RESTもHTTPも、HTTPメソッドをCRUDと同一視する人を除いて、CRUDとは何の関係もありません。RESTは、表現を転送してリソースの状態を操作することです。適切なセマンティクスを使用してリソースに表現を転送することで、達成したいことは何であれ。「ピュアメソッドコール」または「ビジネスロジック」という用語は、「HTTPはトランスポート用である」と簡単に暗示するため、注意してください。メールを送信する必要がある場合、ゲートウェイリソースにPOST、アカウントにマージする必要がある場合は、新しいものを作成し、他の2つのPOST表現を作成するなど
Jan Algermissen

9
:Googleがどうするかも参照してくださいgooglecode.blogspot.com/2010/03/...
マリウス

4
williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot PATCH [{"op": "test"、 "path": "/ a / b / c"、 "value" : "foo"}、{"op": "remove"、 "path": "/ a / b / c"}、{"op": "add"、 "path": "/ a / b / c" 、 "value":["foo"、 "bar"]}、{"op": "replace"、 "path": "/ a / b / c"、 "value":42}、{"op": "move"、 "from": "/ a / b / c"、 "path": "/ a / b / d"}、{"op": "copy"、 "from": "/ a / b / d "、" path ":" / a / b / e "}]
intotecho

48

部分的な更新にはPOSTを使用する必要があります。

顧客123のフィールドを更新するには、/ customer / 123へのPOSTを実行します。

ステータスのみを更新する場合は、/ customer / 123 / statusにPUTすることもできます。

一般に、GETリクエストには副作用がありません。PUTはリソース全体を書き込み/置換するためのものです。

これは、次のようにHTTPから直接続きます。http//en.wikipedia.org/wiki/HTTP_PUT#Request_methods


1
@John Saunders POSTは、必ずしもURIからアクセス可能な新しいリソースを作成する必要はありません。tools.ietf.org
html

10
@wsorensen:新しいURLにする必要がないことはわかっていますが、POST /customer/123は論理的に顧客123の下にある明白なものを作成する必要があると考えていました。おそらく注文ですか?/customer/123/statusPOSTは、POSTが/customers暗黙的に作成されたstatusと仮定して(そしてそれが正当なRESTであると仮定して)、より意味をなさそうです。
John Saunders、

1
@John Saunders:実際には、特定のURIにあるリソースのフィールドを更新する場合、POSTはPUTよりも意味があり、UPDATEがないため、RESTサービスでよく使用されます。/ customersへのPOSTは新しい顧客を作成する可能性があり、/ customer / 123 / statusへのPUTは仕様の言葉と一致する可能性がありますが、ベストプラクティスとしては、/にPOSTしない理由はないと思いますcustomer / 123でフィールドを更新します-簡潔で、理にかなっており、仕様に厳密に反するものではありません。
wsorenson

8
POSTリクエストはべき等ではありませんか?エントリの更新はべき等なので、代わりにPUTにする必要がありますか?
Martin Andersson、

1
@MartinAndersson- POSTリクエストは非べき等である必要ありません。そして前述のように、PUTリソース全体を置き換える必要があります。
Halle Knast 16

10

部分的な更新にはPATCHを使用する必要があります-json-patchドキュメントを使用します(http://tools.ietf.org/html/draft-ietf-appsawg-json-patch-08またはhttp://www.mnot.net/を参照) blog / 2012/09/05 / patch)またはXMLパッチフレームワーク(http://tools.ietf.org/html/rfc5261を参照)。ただし、私の意見では、json-patchは、お客様の種類のビジネスデータに最適です。

JSON / XMLパッチドキュメントを使用したPATCHには、部分的な更新のための非常に厳密な転送セマンティクスがあります。元のドキュメントの変更されたコピーでPOSTの使用を開始すると、部分的な更新のために、欠損値(またはnull値)が「このプロパティを無視する」または「このプロパティを空の値」-そして、ハッキングされたソリューションのうさぎの穴につながり、最終的には独自の種類のパッチ形式になります。

詳細については、http//soabits.blogspot.dk/2013/01/http-put-patch-or-post-partial-updates.htmlをご覧ください


その間、json-patchxml-patchのRFC が完成していることに注意してください。
ボッチニアク2014

8

同様の問題が発生しています。単一のフィールドのみを更新したい場合、サブリソースのPUTは機能するようです。ただし、多くのことを更新したい場合があります。一部のエントリを変更するオプションを持つリソースを表すWebフォームを考えてみてください。ユーザーがフォームを送信しても、複数のPUTが発生することはありません。

ここに私が考えることができる2つの解決策があります:

  1. リソース全体でPUTを実行します。サーバー側で、リソース全体を含むPUTが変更されていないすべての値を無視するというセマンティクスを定義します。

  2. 部分的なリソースでPUTを実行します。サーバー側で、これのセマンティクスをマージと定義します。

2は単なる1の帯域幅最適化です。リソースが必須フィールドであることをリソースが定義している場合、1が唯一のオプションです(protoバッファーと考えてください)。

これらの両方のアプローチの問題は、フィールドをクリアする方法です。フィールドのクリアを引き起こす特別なnull値を定義する必要があります(特にprotoバッファーにはnull値が定義されていないため、protoバッファーに対して)。

コメント?


2
これは、別の質問として投稿された場​​合により役立ちます。
intotecho

6

ステータスを変更するためのRESTfulなアプローチは、リソースのステータスを説明する論理サブリソースを使用することだと思います。このIMOは、ステータスのセットが少ない場合に非常に便利でクリーンです。顧客リソースに既存の操作を強制することなく、APIをより表現力のあるものにします。

例:

POST /customer/active  <-- Providing entity in the body a new customer
{
  ...  // attributes here except status
}

POSTサービスは、次のIDを持つ新しく作成された顧客を返す必要があります。

{
    id:123,
    ...  // the other fields here
}

作成されたリソースのGETは、リソースの場所を使用します。

GET /customer/123/active

GET / customer / 123 / inactiveは404を返す必要があります

PUT操作の場合、Jsonエンティティを提供せずにステータスを更新するだけです

PUT /customer/123/inactive  <-- Deactivating an existing customer

エンティティを提供すると、顧客のコンテンツとステータスを同時に更新できます。

PUT /customer/123/inactive
{
    ...  // entity fields here except id and status
}

顧客リソースの概念的なサブリソースを作成しています。これは、ロイフィールディングのリソースの定義とも一致します。「...リソースは一連のエンティティへの概念的なマッピングであり、特定の時点でのマッピングに対応するエンティティではありません...」この場合、概念的なマッピングは、status = ACTIVEのアクティブな顧客から顧客へのマッピングです。

読み取り操作:

GET /customer/123/active 
GET /customer/123/inactive

これらの呼び出しの1つを呼び出した直後に、もう一方の呼び出しがステータス404を返す必要がある場合、正常な出力には暗黙のステータスが含まれない場合があります。もちろん、GET / customer / 123?status = ACTIVE | INACTIVEを使用して、顧客リソースを直接クエリすることもできます。

セマンティクスが混乱する可能性があるため、DELETE操作は興味深いものです。ただし、この概念的なリソースに対してその操作を公開しないか、ビジネスロジックに従ってそれを使用するオプションがあります。

DELETE /customer/123/active

これにより、顧客をDELETED / DISABLEDステータスまたは反対のステータス(ACTIVE / INACTIVE)にすることができます。


どのようにしてサブリソースにアクセスしますか?
MStodd 2015

私は回答をより明確にするためにリファクタリングしました
raspacorp '17 / 07/17

5

拡張質問に追加するもの。多くの場合、複雑なビジネスアクションを完全に設計できると思います。しかし、思考の方法/手順のスタイルを放棄し、リソースや動詞でもっと考える必要があります。

メール送信


POST /customers/123/mails

payload:
{from: x@x.com, subject: "foo", to: y@y.com}

このリソース+ POSTの実装は、メールを送信します。必要に応じて、/ customer / 123 / outboxなどを提供し、/ customer / mails / {mailId}へのリソースリンクを提供できます。

顧客数

これを検索リソースのように扱うことができます(ページングとnum-found情報を含む検索メタデータを含み、顧客数を提供します)。


GET /customers

response payload:
{numFound: 1234, paging: {self:..., next:..., previous:...} customer: { ...} ....}


POSTサブリソースでフィールドを論理的にグループ化する方法が好きです。
gertas 2014

3

PUTを使用して、不完全/部分的なリソースを更新します。

jObjectをパラメーターとして受け入れ、その値を解析してリソースを更新できます。

以下は、参考として使用できる関数です。

public IHttpActionResult Put(int id, JObject partialObject)
{
    Dictionary<string, string> dictionaryObject = new Dictionary<string, string>();

    foreach (JProperty property in json.Properties())
    {
        dictionaryObject.Add(property.Name.ToString(), property.Value.ToString());
    }

    int id = Convert.ToInt32(dictionaryObject["id"]);
    DateTime startTime = Convert.ToDateTime(orderInsert["AppointmentDateTime"]);            
    Boolean isGroup = Convert.ToBoolean(dictionaryObject["IsGroup"]);

    //Call function to update resource
    update(id, startTime, isGroup);

    return Ok(appointmentModelList);
}

2

アップデートについて。

CRUDの概念は、API設計に関して混乱を引き起こしたと思います。CRUDは、データに対して実行する基本的な操作の一般的な低レベルの概念であり、HTTP動詞は、CRUD操作にマップする場合としない場合がある(21年前に作成された)単なる要求メソッドです。実際、HTTP 1.0 / 1.1仕様でCRUDの頭字語の存在を見つけてください。

実用的な規則を適用する非常によく説明されたガイドは、GoogleクラウドプラットフォームAPIドキュメントにあります。リソースベースのAPIの作成の背後にある概念について説明します。操作よりも大量のリソースを強調するものであり、説明しているユースケースが含まれます。彼らの製品にとっては単なるコンベンションデザインですが、それは非常に理にかなっていると思います。

ここでの基本概念(および多くの混乱を招くもの)は、「メソッド」とHTTP動詞の間のマッピングです。1つは、APIがどのタイプのリソース(たとえば、顧客のリストを取得するか、電子メールを送信するか)に対して行う「操作」(メソッド)を定義することであり、もう1つはHTTP動詞です。使用する予定のメソッドと動詞、およびそれらの間マッピングの両方の定義が必要です。

操作は、標準的な方法で正確にマッピングされない場合にも(すなわち言うListGetCreateUpdateDeleteこの場合)、一つのような、「カスタムメソッド」を使用することができるBatchGetいくつかのオブジェクトID入力に基づいて複数のオブジェクトを取得している、またはSendEmail


2

RFC 7396JSONマージパッチ(質問が投稿されてから4年後に公開)は、形式と処理ルールの観点からPATCHのベストプラクティスを説明しています。

簡単に言うと、application / merge-patch + json MIMEメディアタイプと、変更/追加/削除するパーツのみを表すボディを含むHTTP PATCHをターゲットリソースに送信し、以下の処理ルールに従います。

ルール

  • 提供されたマージパッチにターゲット内に表示されないメンバーが含まれている場合、それらのメンバーが追加されます。

  • ターゲットにメンバーが含まれている場合、値は置き換えられます。

  • マージパッチのnull値には、ターゲットの既存の値の削除を示す特別な意味が与えられます。

上記のルールを説明するテストケースの例(そのRFCの付録にあるとおり):

 ORIGINAL         PATCH           RESULT
--------------------------------------------
{"a":"b"}       {"a":"c"}       {"a":"c"}

{"a":"b"}       {"b":"c"}       {"a":"b",
                                 "b":"c"}
{"a":"b"}       {"a":null}      {}

{"a":"b",       {"a":null}      {"b":"c"}
"b":"c"}

{"a":["b"]}     {"a":"c"}       {"a":"c"}

{"a":"c"}       {"a":["b"]}     {"a":["b"]}

{"a": {         {"a": {         {"a": {
  "b": "c"}       "b": "d",       "b": "d"
}                 "c": null}      }
                }               }

{"a": [         {"a": [1]}      {"a": [1]}
  {"b":"c"}
 ]
}

["a","b"]       ["c","d"]       ["c","d"]

{"a":"b"}       ["c"]           ["c"]

{"a":"foo"}     null            null

{"a":"foo"}     "bar"           "bar"

{"e":null}      {"a":1}         {"e":null,
                                 "a":1}

[1,2]           {"a":"b",       {"a":"b"}
                 "c":null}

{}              {"a":            {"a":
                 {"bb":           {"bb":
                  {"ccc":          {}}}
                   null}}}

1

http://www.odata.org/をチェックしてください。

これはMERGEメソッドを定義しているので、あなたの場合は次のようになります:

MERGE /customer/123

<customer>
   <status>DISABLED</status>
</customer>

statusプロパティのみが更新され、他の値は保持されます。


あるMERGE有効なHTTP動詞は?
John Saunders

3
PATCHを見てください。これは間もなく標準HTTPになり、同じことを行います。
Jan Algermissen、2010年

@John Saundersはい、それは拡張メソッドです。
Max Toro

FYI MERGEはOData v4から削除されました。 docs.oasis-open.org/odata/new-in-odata/v4.0/cn01/…をMERGE was used to do PATCH before PATCH existed. Now that we have PATCH, we no longer need MERGE. 参照してください
tanguy_k

0

それは問題ではありません。RESTに関しては、キャッシュできないためGETを実行できませんが、POSTまたはPATCHまたはPUTなどを使用するかどうか、およびURLがどのように表示されるかは関係ありません。RESTを実行している場合、重要なのは、サーバーからリソースの表現を取得するときに、その表現がクライアントの状態遷移オプションを提供できることです。

GET応答に状態遷移があった場合、クライアントはそれらを読み取る方法を知るだけでよく、サーバーは必要に応じてそれらを変更できます。ここでは、更新はPOSTを使用して行われますが、PATCHに変更された場合、またはURLが変更された場合でも、クライアントは更新方法を知っています。

{
  "customer" :
  {
  },
  "operations":
  [
    "update" : 
    {
      "method": "POST",
      "href": "https://server/customer/123/"
    }]
}

クライアントがあなたに返すために必要な/オプションのパラメータをリストするまで行くことができます。これはアプリケーションによって異なります。

事業運営に関する限り、それは顧客リソースからリンクされた別のリソースである可能性があります。顧客に電子メールを送信する場合、そのサービスはPOST可能な独自のリソースである可能性があるため、顧客リソースに次の操作を含めることができます。

"email":
{
  "method": "POST",
  "href": "http://server/emailservice/send?customer=1234"
}

いくつかの優れたビデオ、およびプレゼンターのRESTアーキテクチャの例はこれらです。StormpathはGET / POST / DELETEのみを使用します。RESTは、使用する操作やURLの外観(GETはキャッシュ可能であることを除く)とは何の関係もないため、問題ありません。

https://www.youtube.com/watch?v=pspy1H6A3FM
https://www.youtube.com/watch?v=5WXYw4J4QOU
http://docs.stormpath.com/rest/quickstart/

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.