web-dev-qa-db-ja.com

REST API - 実際の例を使ったPUTとPATCH

まず第一に、いくつかの定義:

PUTは セクション9.6 RFC 2616 で定義されています。

PUTメソッドは、囲まれたエンティティが提供されたRequest-URIの下に格納されることを要求します。 Request-URIが既存のリソースを参照する場合、囲まれたエンティティは、Originサーバにあるエンティティの修正版と見なされるべきです(SHOULD)。 Request-URIが既存のリソースを指しておらず、そのURIが要求元のユーザーエージェントによって新しいリソースとして定義されることができる場合、OriginサーバはそのURIを使ってリソースを作成することができます。

PATCHは RFC 5789 で定義されています。

PATCHメソッドは、リクエストエンティティに記述されているの変更のセットをRequest-URIで識別されるリソースに適用することを要求します。

また、 RFC 2616のセクション9.1.2 によると、PUTは冪等ではありませんがPUTは冪等です。

それでは、実際の例を見てみましょう。データ/users{username: 'skwee357', email: '[email protected]'}にPOSTを実行し、サーバーがリソースを作成できる場合は、201とリソースの場所(/users/1と仮定)で応答し、GET /users/1を呼び出すと{id: 1, username: 'skwee357', email: '[email protected]'}が返されます。

今度は私が私の電子メールを修正したいと言うことができます。電子メールの修正は「一連の変更」と見なされるため、「 パッチ文書 」で/users/1を修正する必要があります。私の場合はjson {email: '[email protected]'}になります。その後、サーバーは200を返します(許可があれば)。これは私に最初の質問をもたらします:

  • パッチはべき等ではありません。それは、RFC 2616とRFC 5789でそう言いました。しかし、私が同じPATCHリクエストを(私の新しいEメールで)発行すると、同じリソース状態になります(私のEメールは要求された値に変更されます)。なぜPATCHが冪等ではないのですか?

PATCHは比較的新しい動詞(2010年3月に導入されたRFC)であり、「パッチを当てる」または一連のフィールドを変更するという問題を解決するようになりました。 PATCHが導入される前は、誰もがPUTを使ってリソースを更新していました。しかし、PATCHが導入された後、PUTが何に使用されているのか混乱していますか?そしてこれは私に第二の(そして主な)質問をもたらします:

  • PUTとPATCHの本当の違いは何ですか?私はPUTが特定のリソースの下でreplaceentity全体に使われるかもしれないところを読んだので、完全なエンティティを送るべきです(PATCHのような属性のセットの代わりに)。そのような場合の本当の実用的な使い方は何ですか?特定のリソースURI下のエンティティをいつ置換または上書きしたいですか、またそのような操作がエンティティの更新またはパッチ適用と見なされないのはなぜですか?私がPUTで見た唯一の実用的な使用例は、コレクションに対してPUTを発行すること、すなわちコレクション全体を置き換えるための/usersです。 PATCHが導入された後は、特定のエンティティに対してPUTを発行しても意味がありません。私が間違っている?
536

_ note _ :私が最初にRESTについて読んだとき、べき等性は正しいことを試みるための混乱を招く概念でした。さらなるコメント(および Jason Hoetgerの答え )が示しているように、私はまだ私の最初の答えではそれを完全に正しく理解できませんでした。しばらくの間、私は効果的にJasonを盗用することを避けるためにこの答えを広範囲に更新することに抵抗しました、しかし、私はそれを編集しています。

私の答えを読んだ後は、この質問に対する Jason Hoetgerの優秀な答え も読んでおくことをお勧めします。そして、Jasonから盗むことなく、私の答えをもっと良くするようにします。

PUTはなぜ冪等詞ですか?

あなたがあなたのRFC 2616引用で述べたように、PUTはべき乗と見なされます。リソースをPUTするときには、次の2つの前提が効いています。

  1. コレクションではなくエンティティを参照しています。

  2. 指定したエンティティは完全です(全体エンティティ)。

例を見てみましょう。

{ "username": "skwee357", "email": "[email protected]" }

あなたがこのドキュメントを/usersにPOSTしているとしたら、あなたはそのようなエンティティを取り戻すかもしれません。

## /users/1

{
    "username": "skwee357",
    "email": "[email protected]"
}

後でこのエンティティを変更したい場合は、PUTとPATCHのどちらかを選択します。 PUTは次のようになります。

PUT /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // new email address
}

PATCHを使っても同じことができます。これは次のようになります。

PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

あなたはこれら二つの間にすぐに違いに気付くでしょう。 PUTにはこのユーザーのすべてのパラメーターが含まれていましたが、PATCHには変更されているもの(email)のみが含まれていました。

PUTを使用するとき、あなたは完全な実体を送り、そしてその完全な実体置き換えそのURIの既存の実体を仮定します。上記の例では、PUTとPATCHは同じ目標を達成します。どちらもこのユーザーのEメールアドレスを変更します。しかしPUTはエンティティー全体を置き換えることによってそれを処理しますが、PATCHは提供されたフィールドのみを更新し、他のフィールドはそのままにします。

PUTリクエストにはエンティティ全体が含まれるため、同じリクエストを繰り返し発行した場合でも、常に同じ結果になります(送信したデータはエンティティのデータ全体になります)。したがって、PUTはべき等です。

PUTの使い方が間違っている

PUTリクエストで上記のPATCHデータを使用するとどうなりますか?

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"
}
PUT /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "email": "[email protected]"      // new email address... and nothing else!
}

(私は、この質問の目的のために、サーバーには特定の必須フィールドがなく、これが起こることを許可すると想定しています...実際にはそうではないかもしれません。)

PUTを使用していたがemailしか提供していなかったので、今ではこれがこのエンティティの唯一のものです。これによりデータが失われました。

この例は説明のためにここにあります - 実際にこれをしないでください。このPUTリクエストは技術的には冪等ですが、それはそれがひどい、壊れたアイデアではないという意味ではありません。

PATCHはどのようにしてべき乗なのでしょうか。

上記の例では、PATCH wasべき等です。変更を加えましたが、同じ変更を何度も繰り返しても、常に同じ結果が返されます。電子メールアドレスを新しい値に変更しました。

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // email address was changed
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // nothing changed since last GET
}

私のオリジナルの例、正確さのために固定

私はもともと私は非冪等性を示していると思った例を持っていましたが、それらは誤解を招くような/間違っていました。私は例を残しますが、それらを使って異なることを説明します。同じエンティティに対して複数のPATCHドキュメントを作成し、異なる属性を変更しても、PATCHは冪等ではないということです。

昔、ユーザーが追加されたとしましょう。これはあなたが始めている状態です。

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "Zip": "10001"
}

PATCHの後に、修正されたエンティティがあります。

PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",    // the email changed, yay!
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "Zip": "10001"
}

その後、PATCHを繰り返し適用しても、引き続き同じ結果が得られます。電子メールは新しい値に変更されました。 Aが入って、Aが出てくるので、これはべき等です。

1時間後、コーヒーを飲みながら休憩を取りに行った後、他の人が自分のパッチと一緒に来ます。郵便局はいくつかの変更を加えているようです。

PATCH /users/1
{"Zip": "12345"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",  // still the new email you set
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "Zip": "12345"                      // and this change as well
}

郵便局からのこのPATCHは電子メールには関係ありません。郵便番号だけを繰り返し適用しても同じ結果になります。郵便番号は新しい値に設定されます。 Aが入り、Aが出るので、これはべき等です。

次の日、あなたはもう一度あなたのPATCHを送ることに決めます。

PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "Zip": "12345"
}

あなたのパッチは昨日と同じ効果があります:それはEメールアドレスを設定します。 Aが入って、Aが出てきたので、これもべき等です。

私の最初の答えで間違っていたこと

私は重要な区別をしたい(私の最初の答えでは間違っていたこと)。多くのサーバはあなたの修正があれば、新しいエンティティの状態を送り返すことであなたのRESTリクエストに応答します。そのため、このresponseを取得した場合、郵便番号は前回受信したものではないため、昨日戻ってきたものとは異なります _とは異なります。ただし、あなたのリクエストは郵便番号には関係なく、電子メールにのみ関係していました。ですから、あなたのPATCH文書はまだべき等です - あなたがPATCHで送ったEメールはエンティティのEメールアドレスです。

それでは、いつPATCHがべき乗ではないのでしょうか。

この質問を詳しく扱うために、 Jason Hoetger's answer を参照してください。私は正直に言って、私がこの部分にすでに答えている以上に答えられるとは思わないので、そのままにします。

752
Dan Lowe

Dan Loweの優れた答えは、PUTとPATCHの違いについてのOPの質問に非常に徹底的に答えましたが、PATCHがべき乗ではないという問題に対するその答えはまったく正しくありません。

なぜPATCHがべき乗ではないのかを示すために、べき乗の定義から始めるのが助けになります( Wikipedia から):

べき等詞という用語は、1回または複数回実行しても同じ結果になる演算を表すためにより包括的に使用されています[...]べき等函数は、f(f(x))という性質を持つものです。任意の値xに対して= f(x)。

よりアクセスしやすい言語では、べき等のPATCHは次のように定義できます。パッチ文書を使ってリソースをパッチした後、同じパッチ文書を使って同じリソースへの後続のPATCH呼び出しはすべて、リソースを変更しません。

反対に、べき等でない操作はf(f(x))!= f(x)であるもので、これはPATCHについては次のように記述できます。同じパッチドキュメントを持つ同じリソースに do リソースを変更します。

べき乗でないPATCHを説明するために、/ usersリソースがあり、GET /usersを呼び出すとユーザーのリストが返されるとします。

[{ "id": 1, "username": "firstuser", "email": "[email protected]" }]

OPの例のように、PATCHing/users/{id}ではなく、サーバーがPATCHing/usersを許可するとします。このPATCHリクエストを発行しましょう。

PATCH /users
[{ "op": "add", "username": "newuser", "email": "[email protected]" }]

私たちのパッチドキュメントはnewuserと呼ばれる新しいユーザーをユーザーのリストに追加するようにサーバーに指示します。初めてこれを呼び出した後、GET /usersは以下を返します。

[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
 { "id": 2, "username": "newuser", "email": "[email protected]" }]

さて、上記のようにまったく同じ PATCHリクエストを発行すると、どうなりますか? (この例では、/ usersリソースでユーザー名の重複が許可されているとします。) "op"は "add"なので、新しいユーザーがリストに追加され、後続のGET /usersは次のように返します。

[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
 { "id": 2, "username": "newuser", "email": "[email protected]" },
 { "id": 3, "username": "newuser", "email": "[email protected]" }]

まったく同じエンドポイントに対してまったく同じ PATCHを発行したにもかかわらず、/ usersリソースが再度を変更しました。 PATCHがf(x)の場合、f(f(x))はf(x)と同じではないため、 この特定のPATCHはべき乗ではありません

PATCHはべき乗であることをギャランティではありませんが、あなたが特定のサーバーのすべてのPATCH操作を同数にすることを妨げるPATCH仕様には何もありません。 RFC 5789は、べき等のPATCH要求からの利点さえも期待しています。

PATCH要求は、べき等になるように発行することができます。これは、同じリソース上の同じリソース上の2つのPATCH要求間の衝突による悪い結果を防ぐのにも役立ちます。

Danの例では、彼のPATCH操作は実際にはべき等です。その例では、/ users/1エンティティはPATCHリクエスト間で変更されましたが、から PATCHリクエストは変更されませんでした。郵便番号が変更されたのは、実際にはPost Officeのdifferentパッチドキュメントでした。郵便局の異なるPATCHは異なる操作です。 PATCHがf(x)の場合、郵便局のPATCHはg(x)です。べき等性はf(f(f(x))) = f(x)と述べていますが、f(g(f(x)))について保証するものではありません。

257
Jason Hoetger

私はこれについても興味があり、いくつかの興味深い記事を見つけました。私はあなたの質問に完全には答えないかもしれませんが、これは少なくとももう少しの情報を提供します。

http://restful-api-design.readthedocs.org/en/latest/methods.html

HTTP RFCは、PUTがリクエストエンティティとして完全に新しいリソース表現を取らなければならないと規定しています。これは、例えば特定の属性のみが提供されている場合、それらを削除する(つまりnullに設定する)ことを意味します。

それを考えると、PUTはオブジェクト全体を送信する必要があります。例えば、

/users/1
PUT {id: 1, username: 'skwee357', email: '[email protected]'}

これは事実上Eメールを更新するでしょう。 PUTがあまりにも効果的ではないかもしれない理由はあなたの唯一の本当に1つのフィールドを修正して、そしてユーザー名を含んでいることが一種の役に立たないということです。次の例はその違いを示しています。

/users/1
PUT {id: 1, email: '[email protected]'}

PUTが仕様に従って設計されている場合、PUTはユーザー名をnullに設定し、次のようになります。

{id: 1, username: null, email: '[email protected]'}

PATCHを使用するときは、指定したフィールドを更新するだけで、残りはその例のようにします。

以下のPATCHの見方は、今まで見たことがない少し異なります。

http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/ /

PUT要求とPATCH要求の違いは、Request-URIで識別されるリソースを変更するためにサーバーが囲まれたエンティティを処理する方法に反映されています。 PUT要求では、囲まれたエンティティはOriginサーバに保存されているリソースの修正バージョンであると見なされ、クライアントは保存されているバージョンを置き換えることを要求しています。しかしPATCHでは、囲まれたエンティティはOriginサーバ上に現在存在しているリソースをどのように修正して新しいバージョンを生成するかを記述した一連の命令を含みます。 PATCHメソッドはRequest-URIによって識別されたリソースに影響を与えます。また、他のリソースにも副作用があるかもしれません。すなわち、PATCHを適用することによって新しいリソースを作成するか、または既存のリソースを修正することができる。

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "[email protected]" }
]

あなたはフィールドを更新する方法としてPATCHを多かれ少なかれ扱っています。そのため、部分オブジェクトを送信するのではなく、操作を送信します。すなわち、電子メールを価値のあるものに置き換えます。

記事はこれで終わります。

Fieldingの論文ではリソースを部分的に変更する方法を定義していないため、PATCHは本当にREST AP​​I用に設計されたものではありません。しかし、Roy Fielding自身は、部分的なPUTがRESTfulではないため、PATCHは最初のHTTP/1.1提案のために作成されたものであると述べました。もちろん完全な表現を転送しているのではありませんが、RESTは表現が完全である必要はありません。

今、私は多くのコメンテーターが指摘しているように私が記事に特に同意するかどうかわからない。部分的な表現を介して送信すると、簡単に変更内容を説明できます。

私にとっては、私はPATCHを使って混乱しています。私がこれまでに気づいた唯一の本当の違いはPUTが欠損値をnullに "設定"すべきであるということであるので、私はPUTをPATCHとして扱うことが大部分です。それを行うための「最も正しい」方法ではないかもしれませんが、幸運なコーディングは完璧です。

70
Kalel Wade

PUTとPATCHの違いは、

  1. PUTはべき乗である必要があります。それを実現するためには、完全なリソース全体をリクエストボディに入れる必要があります。
  2. PATCHは冪等でなくてもかまいません。これは、あなたが説明したケースのようないくつかのケースではそれがべき乗であることを意味します。

PATCHは、サーバーにリソースの変更方法を指示するための「パッチ言語」を必要とします。呼び出し側とサーバーは、「追加」、「置換」、「削除」などの「操作」を定義する必要があります。例えば:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "state": "NY",
  "Zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "[email protected]"},
  {"operation": "delete", "field": "Zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "state": "NY",
  "address": "123 main street",
}

明示的な「操作」フィールドを使用する代わりに、パッチ言語は次のような規約を定義することで暗黙的にすることができます。

pATCHリクエストボディ内:

  1. フィールドの存在は、そのフィールドを「置換」または「追加」することを意味します。
  2. フィールドの値がnullの場合は、そのフィールドを削除することを意味します。

上記の規則では、この例のPATCHは次の形式になります。

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "[email protected]",
  "Zip":
}

どちらがより簡潔でユーザーフレンドリーに見えます。しかし、ユーザーは根本的な規約に注意する必要があります。

上記の操作で、PATCHはまだべき等です。しかし、 "increment"や "append"のような操作を定義すれば、もうそれがべき乗ではないことが簡単にわかります。

12
Bin Ni

以前のコメントで既に引用した RFC 7231のセクション4.2.2 をもっと詳しく引用してコメントさせてください。

そのメソッドを持つ複数の同一リクエストのサーバーへの意図された効果が単一のそのようなリクエストに対する効果と同じであるならば、リクエストメソッドは「べき乗」と見なされます。この仕様で定義されている要求メソッドのうち、PUT、DELETE、および安全な要求メソッドは同じです。

(...)

クライアントがサーバーの応答を読み取ることができる前に通信障害が発生した場合、要求が自動的に繰り返される可能性があるため、べき等法は区別されます。たとえば、クライアントがPUT要求を送信し、応答が受信される前に基礎となる接続が閉じられた場合、クライアントは新しい接続を確立してべき等要求を再試行できます。たとえ元の要求が成功したとしても、応答を異ならせても、要求を繰り返すことは同じ意図された効果を持つことを知っています。

それで、べき等法の繰り返し要求の後に何が「同じ」であるべきですか?サーバーの状態やサーバーの応答ではなく、意図した効果です。特に、このメソッドは「クライアントの観点から見て」冪等である必要があります。さて、ここで私はここで盗用したくない Dan Loweの答え の最後の例が(より自然な方法で)PATCH要求が非冪等であることを示していることを示していると思います。 Jason Hoetger's answer )の例よりも。

実際、最初のクライアントに対して明示的に1つの可能なintendを作成することによって、例をもう少し正確にしましょう。このクライアントがプロジェクトのユーザーのリストを調べて、自分のEメールand Zipコードをチェックするとしましょう。彼はユーザー1から始め、Zipは正しいがEメールは間違っていることに気付きました。彼はこれをPATCHリクエストで修正することを決定しました。

PATCH /users/1
{"email": "[email protected]"}

これが唯一の修正だからです。現在、ネットワークの問題が原因で要求が失敗し、数時間後に自動的に再送信されます。その間に、別のクライアントが(誤って)ユーザー1のZipを変更しました。次に、同じPATCH要求を2回送信しても、クライアントの意図する効果を達成できません。郵便番号したがって、この方法はRFCの意味ではべき等ではありません。

その代わりに、クライアントがPUT要求を使用してEメールを修正し、Eメールとともにユーザー1のすべてのプロパティーをサーバーに送信すると、後で要求を再送信してユーザー1が変更された場合でも、意図した効果が得られます。その間--- 2番目のPUTリクエストは最初のリクエスト以降のすべての変更を上書きするので。

2
Rolvernew
0
jobima