1. ホーム

[解決済み】REST APIでのPUTメソッドとPATCHメソッドの使い分け 実生活でのシナリオ

2022-03-23 21:51:01

質問

まず、いくつかの定義について。

PUTは、以下のように定義されています。 9.6項 RFC 2616 :

PUTメソッドは、指定されたRequest-URIの下に同封のエンティティを格納するよう要求します。Request-URIが既に存在するリソースを参照する場合、同封のエンティティは、そのリソースの下に保存されます。 オリジンサーバーに存在するものを修正したものとみなされるべきです(SHOULD) . Request-URIが既存のリソースを指しておらず、そのURIがリクエストする ユーザーエージェントによって新しいリソースとして定義可能な場合、オリジ ンサーバーはそのURIを持つリソースを作成することができる。

PATCHは、以下のように定義されています。 RFC 5789 :

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

また RFC 2616 9.1.2項 PUTはべき乗であるが、PATCHはべき乗でない。

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

ここで、自分のメールを修正したいとします。電子メールの修正は、一連の変更とみなされます。 /users/1 を " に置き換えてください。 パッチドキュメント "です。私の場合、それはjsonドキュメントになります。 {email: '[email protected]'} . その後、サーバーは200を返します(許可に問題がないと仮定して)。これが最初の質問です。

  • PATCHはべき乗ではありません。RFC2616とRFC5789にそのように書かれています。しかし、もし私が同じPATCHリクエストを(私の新しいEメールで)発行すれば、同じリソースの状態(私のEメールがリクエストされた値に変更されている)を得ることができます。なぜ、PATCHはべき乗ではないのでしょうか?

PATCHは比較的新しい動詞で(RFCは2010年3月に導入)、フィールドのセットをパッチする、または修正するという問題を解決するために生まれました。PATCHが導入される前は、誰もがリソースを更新するためにPUTを使用していました。しかし、PATCHが導入された後は、PUTが何のために使われるのかが分からなくなりました。そして、これが私の2つ目の(そして主要な)質問へとつながります。

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

解決方法は?

注意事項 : 私が初めてRESTについて時間をかけて読んだとき、idempotenceは正しく理解しようとすると混乱する概念でした。最初の回答ではまだ正しく理解できていませんでしたが、その後のコメント(と Jason Hoetgerの回答 が示すとおりです。しばらくの間、私はジェイソンの盗作を避けるために、この回答を大幅に更新することに抵抗がありましたが、(コメントで)頼まれたので、今編集しています。

私の回答を読んだ後、次の記事も読むことをお勧めします。 Jason Hoetgerの素晴らしい回答 この質問に対する私の回答は、単にジェイソンから盗むだけでなく、より良いものになるように努力します。

なぜPUTはべき乗なのですか?

RFC2616の引用にあるように、PUTはべき乗と考えられています。リソースをPUTするとき、この2つの前提が働いています。

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

  2. 供給しているエンティティは完全なものです( 全体 のエンティティ)。

例の一つを見てみましょう。

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

このドキュメントをPOSTすると /users のようなエンティティが返されるかもしれません。

## /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
}

この2つの違いはすぐにお分かりになると思います。PUTはこのユーザーに関するすべてのパラメータを含んでいましたが、PATCHは変更されているものだけを含んでいます( email ).

PUTを使用する場合、完全なエンティティを送信することが前提であり、その完全なエンティティは が置き換わります。 は、そのURIに存在する任意のエンティティです。上記の例では、PUTとPATCHは同じ目的を達成します。どちらもこのユーザーの電子メールアドレスを変更します。しかし、PUTはエンティティ全体を置き換えることでそれを処理し、PATCHは供給されたフィールドのみを更新し、他のフィールドはそのままにしておきます。

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

PUTの間違った使い方

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

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 でした。 べき乗です。あなたは変更を加えましたが、同じ変更を何度も行うと、常に同じ結果が返されます:電子メールアドレスを新しい値に変更したのです。

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を持ってやってきます。どうやら郵便局で何か変更があったようだ。

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"
}

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

最初の回答で間違えたこと

重要な区別をしたいと思います(私の最初の回答で間違っていたこと)。多くのサーバーは、あなたのRESTリクエストに応答して、あなたの修正を加えた新しいエンティティの状態を送り返します(もしあれば)。ですから、次のような レスポンス 戻る、それは違う 昨日戻ってきたものと というのは、郵便番号が前回受け取ったものと違うからです。しかし、あなたのリクエストは郵便番号には関係なく、電子メールにのみ関係しました。つまり、PATCH ドキュメントはまだべき乗です。PATCH で送信した電子メールは、現在ではエンティティの電子メールアドレスになっています。

では、PATCHがべき乗でないのはどんな場合でしょうか?

この問題の完全な処理については、再度、以下を参照されたい。 Jason Hoetgerの回答 . 正直なところ、この部分については、彼以上の回答はできないと思うので、この辺にしておくことにする。