在 REST API 现实生活场景中使用 PUT 与 PATCH 方法 [英] Use of PUT vs PATCH methods in REST API real life scenarios

查看:29
本文介绍了在 REST API 现实生活场景中使用 PUT 与 PATCH 方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,一些定义:

PUT 在 第 9.6 节 RFC 2616 中定义:

<块引用>

PUT 方法请求将封闭的实体存储在提供的 Request-URI 下.如果请求 URI 引用了一个已经存在的资源,则包含的实体应该被视为驻留在源服务器上的实体的修改版本.如果 Request-URI 不指向现有资源,并且该 URI 能够被请求的用户代理定义为新资源,则源服务器可以使用该 URI 创建资源.

PATCH 在 RFC 5789 中定义:

<块引用>

PATCH 方法要求一组更改在请求实体被应用到请求所标识的资源上——URI.

也根据 RFC 2616 第 9.1.2 节 PUT 是幂等的,而 PATCH 不是.

现在让我们看一个真实的例子.当我使用数据 {username: 'skwee357', email: 'skwee357@domain.com'} POST 到 /users 并且服务器能够创建资源时,它将响应 201 和资源位置(让我们假设 /users/1)并且任何对 GET /users/1 的下一次调用将返回 {id: 1, 用户名:'skwee357',电子邮件:'skwee357@domain.com'}.

现在让我们说我想修改我的电子邮件.电子邮件修改被认为是一组更改".因此我应该用修补/users/1补丁文件".就我而言,它将是 json 文档:{email: 'skwee357@newdomain.com'}.然后服务器返回 200(假设权限没问题).这让我想到了第一个问题:

  • PATCH 不是幂等的.它在 RFC 2616 和 RFC 5789 中是这样说的.但是,如果我发出相同的 PATCH 请求(使用我的新电子邮件),我将获得相同的资源状态(我的电子邮件被修改为请求的值).为什么 PATCH 不是幂等的?

PATCH 是一个比较新的动词(RFC 于 2010 年 3 月引入),它来解决打补丁"的问题.或修改一组字段.在引入 PATCH 之前,每个人都使用 PUT 来更新资源.但是在引入 PATCH 之后,它让我对 PUT 的用途感到困惑.这让我想到了我的第二个(也是主要的)问题:

  • PUT 和 PATCH 之间的真正区别是什么?我在某处读到 PUT 可能用于替换特定资源下的整个实体,因此应该发送完整实体(而不是像 PATCH 那样的一组属性).这种情况的真正实际用途是什么?您希望何时替换/覆盖特定资源 URI 处的实体,为什么不考虑更新/修补实体的此类操作?我看到的 PUT 的唯一实际用例是在集合上发出 PUT,即 /users 来替换整个集合.引入 PATCH 后,在特定实体上发出 PUT 没有任何意义.我错了吗?

解决方案

注意:当我第一次花时间阅读有关 REST 的内容时,幂等性是一个令人困惑的概念.正如进一步的评论(和 Jason Hoetger 的回答)所显示的那样,我在最初的答案中仍然没有完全正确.有一段时间,我一直拒绝广泛更新这个答案,以避免有效地抄袭 Jason,但我现在正在编辑它,因为,好吧,我被要求(在评论中).

阅读我的回答后,我建议您也阅读Jason Hoetger 对这个问题的出色回答,我会尝试让我的答案更好,而不是简单地从 Jason 那里窃取.

为什么 PUT 是幂等的?

正如您在 RFC 2616 引用中所指出的,PUT 被认为是幂等的.当你放置一个资源时,这两个假设在起作用:

  1. 您指的是实体,而不是集合.

  2. 您提供的实体是完整的(整个实体).

让我们看一下您的示例之一.

{ "username": "skwee357", "email": "skwee357@domain.com";}

如果您按照您的建议将此文档发布到 /users,那么您可能会返回一个实体,例如

##/users/1{用户名":skwee357",电子邮件":skwee357@domain.com"}

如果以后要修改此实体,请在 PUT 和 PATCH 之间进行选择.PUT 可能如下所示:

PUT/users/1{用户名":skwee357",电子邮件":skwee357@gmail.com"//新邮箱地址}

您可以使用 PATCH 完成相同的操作.可能看起来像这样:

PATCH/users/1{电子邮件":skwee357@gmail.com"//新邮箱地址}

您会立即注意到这两者之间的区别.PUT 包含该用户的所有参数,但 PATCH 仅包含正在修改的参数(email).

使用 PUT 时,假定您发送的是完整实体,并且该完整实体替换该 URI 处的任何现有实体.在上面的示例中,PUT 和 PATCH 实现了相同的目标:它们都更改了该用户的电子邮件地址.但是 PUT 通过替换整个实体来处理它,而 PATCH 只更新提供的字段,其他的则不理会.

由于 PUT 请求包含整个实体,如果您重复发出相同的请求,它应该始终具有相同的结果(您发送的数据现在是实体的整个数据).因此PUT是幂等的.

使用 PUT 错误

如果在 PUT 请求中使用上述 PATCH 数据会怎样?

GET/users/1{用户名":skwee357",电子邮件":skwee357@domain.com"}放置/用户/1{电子邮件":skwee357@gmail.com"//新邮箱地址}获取/用户/1{电子邮件":skwee357@gmail.com"//新的电子邮件地址...没有别的!}

(对于这个问题,我假设服务器没有任何特定的必填字段,并且会允许这种情况发生……但现实中可能并非如此.)

由于我们使用了 PUT,但只提供了 email,现在这是该实体中唯一的内容.这导致数据丢失.

此示例用于说明目的 - 切勿实际执行此操作.这个 PUT 请求在技术上是幂等的,但这并不意味着它不是一个糟糕的、破碎的想法.

PATCH 如何是幂等的?

在上面的例子中,PATCH 幂等的.您进行了更改,但如果您一次又一次地进行相同的更改,它总会返回相同的结果:您将电子邮件地址更改为新值.

GET/users/1{用户名":skwee357",电子邮件":skwee357@domain.com"}补丁/用户/1{电子邮件":skwee357@gmail.com"//新邮箱地址}获取/用户/1{用户名":skwee357",电子邮件":skwee357@gmail.com"//电子邮件地址已更改}补丁/用户/1{电子邮件":skwee357@gmail.com"//新的电子邮件地址...再次}获取/用户/1{用户名":skwee357",电子邮件":skwee357@gmail.com"//自上次 GET 以来没有任何变化}

我的原始示例,已修复以提高准确性

我最初有一些我认为显示非幂等性的示例,但它们具有误导性/不正确.我将保留示例,但使用它们来说明不同的事情:针对同一实体的多个 PATCH 文档,修改不同的属性,不会使 PATCH 成为非幂等的.

假设在过去的某个时间,添加了一个用户.这是您开始的状态.

{id":1,姓名":Sam Kwee",电子邮件":skwee357@olddomain.com",地址":123 Mockingbird Lane",城市":纽约",州":纽约",zip":10001"}

在 PATCH 之后,您有一个修改过的实体:

PATCH/users/1{电子邮件":skwee357@newdomain.com"}{id":1,姓名":Sam Kwee","email": "skwee357@newdomain.com",//邮箱变了,耶!地址":123 Mockingbird Lane",城市":纽约",州":纽约",zip":10001"}

如果您随后重复应用 PATCH,您将继续得到相同的结果:电子邮件已更改为新值.A进去,A出来,所以这是幂等的.

一个小时后,在你去泡咖啡休息一下之后,其他人带着他们自己的 PATCH 来了.邮局似乎正在做一些改变.

PATCH/users/1{zip":12345"}{id":1,姓名":Sam Kwee","email": "skwee357@newdomain.com",//还是你设置的新邮箱地址":123 Mockingbird Lane",城市":纽约",州":纽约",zip":12345"//还有这个变化}

由于邮局的这个PATCH本身不关心电子邮件,只关心邮政编码,如果重复应用,也会得到相同的结果:邮政编码设置为新值.A进去,A出来,因此这也是幂等的.

第二天,您决定再次发送您的 PATCH.

PATCH/users/1{电子邮件":skwee357@newdomain.com"}{id":1,姓名":Sam Kwee",电子邮件":skwee357@newdomain.com",地址":123 Mockingbird Lane",城市":纽约",州":纽约",zip":12345"}

您的补丁具有与昨天相同的效果:它设置了电子邮件地址.A进去,A出来,所以这也是幂等的.

我原来的回答有什么问题

我想做出一个重要的区分(我在原始答案中弄错了).许多服务器将通过发回新的实体状态以及您的修改(如果有)来响应您的 REST 请求.所以,当你收到这个回复时,它与你昨天收到的不同,因为邮政编码不是你上次收到的.但是,您的请求与邮政编码无关,只与电子邮件有关.所以您的 PATCH 文档仍然是幂等的 - 您在 PATCH 中发送的电子邮件现在是实体上的电子邮件地址.

那么什么时候 PATCH 不是幂等的?

要全面了解这个问题,我再次建议您参阅 Jason Hoetger 的回答.我只是打算就此搁笔,因为老实说,我认为我无法比他已经回答的更好.

First of all, some definitions:

PUT is defined in Section 9.6 RFC 2616:

The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.

PATCH is defined in RFC 5789:

The PATCH method requests that a set of changes described in the request entity be applied to the resource identified by the Request- URI.

Also according to RFC 2616 Section 9.1.2 PUT is Idempotent while PATCH is not.

Now let us take a look at a real example. When I do POST to /users with the data {username: 'skwee357', email: 'skwee357@domain.com'} and the server is capable of creating a resource, it will respond with 201 and resource location (lets assume /users/1) and any next call to GET /users/1 will return {id: 1, username: 'skwee357', email: 'skwee357@domain.com'}.

Now let us say I want to modify my email. Email modification is considered "a set of changes" and therefore I should PATCH /users/1 with "patch document". In my case it would be the json document: {email: 'skwee357@newdomain.com'}. The server then returns 200 (assuming permission are ok). This brings me to first question:

  • PATCH is NOT idempotent. It said so in RFC 2616 and RFC 5789. However if I issue the same PATCH request (with my new email), I will get the same resource state (with my email being modified to the requested value). Why is PATCH not then idempotent?

PATCH is a relatively new verb (RFC introduced in March 2010), and it comes to solve the problem of "patching" or modifying a set of fields. Before PATCH was introduced, everybody used PUT to update resources. But after PATCH was introduced, it leaves me confused about what PUT is used for. And this brings me to my second (and the main) question:

  • What is the real difference between PUT and PATCH? I have read somewhere that PUT might be used to replace entire entity under specific resource, so one should send the full entity (instead of set of attributes as with PATCH). What is the real practical usage for such case? When would you like to replace / overwrite an entity at a specific resource URI and why is such an operation not considered updating / patching the entity? The only practical use case I see for PUT is issuing a PUT on a collection, i.e. /users to replace the entire collection. Issuing PUT on a specific entity makes no sense after PATCH was introduced. Am I wrong?

解决方案

NOTE: When I first spent time reading about REST, idempotence was a confusing concept to try to get right. I still didn't get it quite right in my original answer, as further comments (and Jason Hoetger's answer) have shown. For a while, I have resisted updating this answer extensively, to avoid effectively plagiarizing Jason, but I'm editing it now because, well, I was asked to (in the comments).

After reading my answer, I suggest you also read Jason Hoetger's excellent answer to this question, and I will try to make my answer better without simply stealing from Jason.

Why is PUT idempotent?

As you noted in your RFC 2616 citation, PUT is considered idempotent. When you PUT a resource, these two assumptions are in play:

  1. You are referring to an entity, not to a collection.

  2. The entity you are supplying is complete (the entire entity).

Let's look at one of your examples.

{ "username": "skwee357", "email": "skwee357@domain.com" }

If you POST this document to /users, as you suggest, then you might get back an entity such as

## /users/1

{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}

If you want to modify this entity later, you choose between PUT and PATCH. A PUT might look like this:

PUT /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // new email address
}

You can accomplish the same using PATCH. That might look like this:

PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

You'll notice a difference right away between these two. The PUT included all of the parameters on this user, but PATCH only included the one that was being modified (email).

When using PUT, it is assumed that you are sending the complete entity, and that complete entity replaces any existing entity at that URI. In the above example, the PUT and PATCH accomplish the same goal: they both change this user's email address. But PUT handles it by replacing the entire entity, while PATCH only updates the fields that were supplied, leaving the others alone.

Since PUT requests include the entire entity, if you issue the same request repeatedly, it should always have the same outcome (the data you sent is now the entire data of the entity). Therefore PUT is idempotent.

Using PUT wrong

What happens if you use the above PATCH data in a PUT request?

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PUT /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "email": "skwee357@gmail.com"      // new email address... and nothing else!
}

(I'm assuming for the purposes of this question that the server doesn't have any specific required fields, and would allow this to happen... that may not be the case in reality.)

Since we used PUT, but only supplied email, now that's the only thing in this entity. This has resulted in data loss.

This example is here for illustrative purposes -- don't ever actually do this. This PUT request is technically idempotent, but that doesn't mean it isn't a terrible, broken idea.

How can PATCH be idempotent?

In the above example, PATCH was idempotent. You made a change, but if you made the same change again and again, it would always give back the same result: you changed the email address to the new value.

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // email address was changed
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // nothing changed since last GET
}

My original example, fixed for accuracy

I originally had examples that I thought were showing non-idempotency, but they were misleading / incorrect. I am going to keep the examples, but use them to illustrate a different thing: that multiple PATCH documents against the same entity, modifying different attributes, do not make the PATCHes non-idempotent.

Let's say that at some past time, a user was added. This is the state that you are starting from.

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

After a PATCH, you have a modified entity:

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",    // the email changed, yay!
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

If you then repeatedly apply your PATCH, you will continue to get the same result: the email was changed to the new value. A goes in, A comes out, therefore this is idempotent.

An hour later, after you have gone to make some coffee and take a break, someone else comes along with their own PATCH. It seems the Post Office has been making some changes.

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

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",  // still the new email you set
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"                      // and this change as well
}

Since this PATCH from the post office doesn't concern itself with email, only zip code, if it is repeatedly applied, it will also get the same result: the zip code is set to the new value. A goes in, A comes out, therefore this is also idempotent.

The next day, you decide to send your PATCH again.

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"
}

Your patch has the same effect it had yesterday: it set the email address. A went in, A came out, therefore this is idempotent as well.

What I got wrong in my original answer

I want to draw an important distinction (something I got wrong in my original answer). Many servers will respond to your REST requests by sending back the new entity state, with your modifications (if any). So, when you get this response back, it is different from the one you got back yesterday, because the zip code is not the one you received last time. However, your request was not concerned with the zip code, only with the email. So your PATCH document is still idempotent - the email you sent in PATCH is now the email address on the entity.

So when is PATCH not idempotent, then?

For a full treatment of this question, I again refer you to Jason Hoetger's answer. I'm just going to leave it at that, because I honestly don't think I can answer this part better than he already has.

这篇关于在 REST API 现实生活场景中使用 PUT 与 PATCH 方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆