我应该在REST API中使用PATCH还是PUT?


274

我想使用适合以下情况的方法设计其余端点。

有一个小组。每个组都有一个状态。该组可以由管理员激活或停用。

我应该将终点设计为

PUT /groups/api/v1/groups/{group id}/status/activate

要么

PATCH /groups/api/v1/groups/{group id}

with request body like 
{action:activate|deactivate}

1
两者都很好。但是,请看一下JSON PATCH格式的RFC(tools.ietf.org/html/rfc6902)。PATCH希望为有效负载获取某种diff / patch文档(原始JSON并不是其中之一)。
约恩·怀尔德2014年

1
@JørnWildt不,PUT将是一个可怕的选择。你在那放什么 PATCH是唯一明智的选择。好了,在这种情况下,您可以使用问题中提出的PATCH格式,而只需使用PUT方法即可;PUT示例是错误的。
thecoshman 2014年

3
将一个或多个属性公开为客户端可以使用PUT进行获取和修改的独立资源没有错。但是,是的,URL应该是/ groups / api / v1 / groups / {group id} / status,您可以将其设置为“活动”或“非活动”或GET以读取当前状态。
约恩·怀尔德2014年

3
这很好地说明了应如何真正使用PATCH:williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot
rishat

4
activate”不足以实现RESTful结构。您可能正在尝试将其更新status为“有效”或“无效”。在这种情况下,您可以.../status使用正文中的“ active”或“ deactive”字符串进行PATCH 。或者,如果你想在更新一个布尔值status.active,你可以修补,以.../status/active与身体布尔
奥吉·加德纳

Answers:


328

PATCH在更新现有资源-组ID时,此方法是正确的选择。 PUT仅在您要更换时使用在完全资源。

RFC 5789中提供了有关部分资源修改的更多信息。具体地,该PUT方法描述如下:

几个扩展超文本传输​​协议(HTTP)的应用程序需要一项功能来进行部分资源修改。现有的HTTP PUT方法仅允许完全替换文档。该提议添加了新的HTTP方法PATCH,以修改现有的HTTP资源。


1
公平地说,您可以将字符串“ activate”或“ deactivate”放入资源中。因为(似乎)只有一件事需要切换,所以完全替换它并不是一件大事。它确实允许一个(无关紧要)较小的请求。
thecoshman 2014年

35
重要的是要注意,RFC 5789仍处于提议阶段,尚未被正式接受,目前被标记为“存在irrata”。这种“最佳实践”受到了激烈的争论,从技术上讲,PATCH还不是HTTP标准的一部分。
fishpen0

4
几年后仅花了我2美分:您可以将状态本身视为一种资源,如果是这样,对/ status使用PUT从技术上讲将在该端点处替换状态资源。
乔诺·斯图尔特

3
我敢于反对文档,即使它是“ the” RFC。docs指出您应该使用PATCH来修改资源的一部分,但是它忽略了PATCH方法被定义为非等幂方法的重要意义。为什么?如果创建PUT方法时要考虑到整个资源的更新/替换,那么为什么不将PATCH方法创建为像PUT这样的幂等方法,如果其目的只是更新资源的一部分呢?在我看来,更新的幂等性似乎更多不同,例如“ a = 5”(PUT)和“ a = a + 5”(PATCH)。两者都可以更新整个资源。
Mladen B.

179

REST中的R代表资源

(这是不正确的,因为它代表表示形式,但这是记住资源在REST中的重要性的好技巧)。

关于PUT /groups/api/v1/groups/{group id}/status/activate:您更新“激活”。“激活”不是事物,而是动词。动词永远不是好的资源。经验法则:如果动作(动词)在URL中,则可能不是RESTful的

你在做什么呢?您正在“添加”,“删除”或“更新” 组上的激活,或者如果您愿意:在组上操作“状态”资源。就个人而言,我会使用“激活”,因为它们比“状态”的概念不那么模棱两可:创建状态是模棱两可的,而创建激活则不是。

  • POST /groups/{group id}/activation 创建(或请求创建)激活。
  • PATCH /groups/{group id}/activation更新现有激活的一些详细信息。由于一个组只有一个激活,因此我们知道我们指的是什么激活资源。
  • PUT /groups/{group id}/activation插入或替换旧的激活。由于一个组只有一个激活,因此我们知道我们指的是什么激活资源。
  • DELETE /groups/{group id}/activation 将取消或删除激活。

当组的“激活”具有副作用(例如付款,发送邮件等)时,此模式很有用。只有POST和PATCH可能会有这种副作用。例如,当需要删除激活信息时,例如通过邮件通知用户,DELETE不是正确的选择。在这种情况下,你可能想创建一个停用的资源POST /groups/{group_id}/deactivation

遵循这些准则是个好主意,因为此标准合同对您的客户以及客户与您之间的所有代理和层非常清楚,知道什么时候可以重试,什么时候不可以。假设客户端位于不稳定的wifi上,并且用户单击“停用”,这会触发DELETE:如果失败,则客户端可以简单地重试,直到获得404、200或它可以处理的其他任何功能。但是,如果触发了POST to deactivation它,它就知道不重试:POST暗示了这一点。
现在,任何客户端都有合同,遵循该合同,可以防止发送42封“您的组已被停用”的电子邮件,这仅仅是因为其HTTP库不断重试对后端的调用。

更新单个属性:使用PATCH

PATCH /groups/{group id}

如果您希望更新属性。例如,“状态”可以是可以设置的网上论坛的属性。诸如“状态”之类的属性通常是将值限制为白名单的理想选择。示例使用一些未定义的JSON方案:

PATCH /groups/{group id} { "attributes": { "status": "active" } }
response: 200 OK

PATCH /groups/{group id} { "attributes": { "status": "deleted" } }
response: 406 Not Acceptable

替换资源时,没有副作用,请使用PUT。

PUT /groups/{group id}

如果您希望更换整个组。这并不一定意味着服务器实际上会创建一个新组并将旧组扔掉,例如,id可能保持不变。但对于客户来说,这是什么PUT 意味着:客户应承担他得到了一个全新的项目,基于服务器的响应。

如果有PUT请求,客户端应该始终发送整个资源,并拥有创建新项目所需的所有数据:通常与POST创建所需的数据相同。

PUT /groups/{group id} { "attributes": { "status": "active" } }
response: 406 Not Acceptable

PUT /groups/{group id} { "attributes": { "name": .... etc. "status": "active" } }
response: 201 Created or 200 OK, depending on whether we made a new one.

一个非常重要的要求是PUT幂等的:如果在更新组(或更改激活)时需要副作用,则应使用PATCH。因此,当更新导致例如发送邮件时,请不要使用PUT


3
这对我很有帮助。“当组的“激活”具有副作用时,此模式很有用”-这种模式的作用如何,特别是在动作具有副作用而不是与OP初始终点相对应的方面
Abdul

1
@Abdul,这种模式之所以有用,有很多原因,但是有副作用,对于客户来说,动作应该产生什么影响应该是很清楚的。例如,当iOS应用决定将整个通讯录作为“联系人”发送时,应非常清楚联系人的创建,更新,删除等有哪些副作用。例如,为避免大量邮件发送所有联系人。
berkes

1
在RESTfull中,PUT还可以更改实体身份-例如,可能导致并行请求失败的PrimaryKey ID。(例如,更新整个实体需要删除一些行并添加新的行,从而创建新的实体)PATCH永远不能做到这一点,在不影响其他“应用程序”的情况下允许无限数量的PATCH请求
Piotr Kula

1
很有帮助的答案。谢谢!我还要添加一条评论,就像在Luke的回答中指出的那样,指出PUT / PATCH之间的差异不仅是全部/部分更新,而且是幂等性。这不是一个错误,这是一个有意的决定,我认为在确定HTTP方法的用法时不会考虑很多人。
Mladen B.

1
@richremer服务与模型一样,是内部抽象。就像在REST端点和ORM模型甚至数据库表之间要求1-1关系是一种糟糕的抽象一样,暴露Services也是一种糟糕的抽象。您的API外部必须传达域模型。API无关您如何在内部实现它们。您应该可以从ActivationService转到基于CQRS的激活流程,而不必更改API。
berkes

12

我建议您使用PATCH,因为您的资源“组”具有许多属性,但是在这种情况下,您仅更新了激活字段(部分修改)

根据RFC5789(https://tools.ietf.org/html/rfc5789

现有的HTTP PUT方法仅允许完全替换文档。该提议添加了新的HTTP方法PATCH,以修改现有的HTTP资源。

此外,更详细地

PUT和PATCH请求之间的差异体现在服务器处理封闭实体以修改
由Request-URI标识的资源的方式。在PUT请求中,封闭的实体被视为
原始服务器上存储的资源的修改版本,并且客户端正在请求
替换存储的版本。但是,对于PATCH,封闭的实体包含一组指令,这些指令描述了
应如何修改当前驻留在源服务器上的资源以产生新版本。PATCH方法影响由Request-URI标识的资源,并且
可能对其他资源也有副作用。即新资源
可以通过应用
PATCH 来创建或修改现有的。

[RFC2616] 9.1节定义,PATCH既不安全也不具有幂等性。

客户需要选择何时使用PATCH而不是PUT。对于
例如,如果补丁文件大小比大小大
,将在PUT使用的新资源数据,然后它可能是
有意义的使用PUT而不是补丁。与POST进行比较甚至更加困难,因为POST以广泛的方式使用并且可以
并且如果服务器选择包含PUT和PATCH式的操作。如果
操作未以可预测的方式修改Request-URI标识的资源,则应考虑使用POST而不是PATCH
或PUT。

PATCH的响应代码为

之所以使用204响应代码,是因为该响应不携带消息主体(带有200代码的响应将具有该消息主体)。注意,也可以使用其他成功代码。

另请参阅thttp://restcookbook.com/HTTP%20Methods/patch/

注意:实现PATCH的API必须进行原子修补。当GET请求时,资源不可能半修补。


7

由于要使用REST架构样式设计API,因此需要考虑用例,以确定哪些概念足够重要,可以作为资源公开。如果您决定将组的状态公开为子资源,则可以为其提供以下URI,并实现对GET和PUT方法的支持:

/groups/api/groups/{group id}/status

这种用于PATCH进行修改的方法的缺点是,您将无法原子地和事务地对一个组的多个属性进行更改。如果交易变更很重要,请使用PATCH。

如果您决定将状态显示为组的子资源,则它应该是组表示形式中的链接。例如,如果代理获取组123并接受XML,则响应主体可以包含:

<group id="123">
  <status>Active</status>
  <link rel="/linkrels/groups/status" uri="/groups/api/groups/123/status"/>
  ...
</group>

需要超链接来实现超媒体作为 REST体系结构样式的应用程序状态条件的引擎


0

我通常希望更简单一些,例如activate/ deactivatesub-resource(由带有的Link标头链接rel=service)。

POST /groups/api/v1/groups/{group id}/activate

要么

POST /groups/api/v1/groups/{group id}/deactivate

对于用户而言,此接口非常简单,它遵循REST原理,不会使您陷入将“激活”概念化为单独资源的麻烦。


0

实现这种行为的一种可能选择是

PUT /groups/api/v1/groups/{group id}/status
{
    "Status":"Activated"
}

显然,如果有人需要停用它,PUT它将Deactivated在JSON中具有状态。

在需要大规模激活/停用的情况下,PATCH可以进入游戏(不是针对确切的团队,而是针对groups资源:

PATCH /groups/api/v1/groups
{
    { “op”: “replace”, “path”: “/group1/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group7/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group9/status”, “value”: “Deactivated” }
}

通常,这是@Andrew Dobrowolski提出的想法,但在确切实现上略有变化。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.