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

Posted

技术标签:

【中文标题】我应该在我的 REST API 中使用 PATCH 还是 PUT?【英文标题】:Should I use PATCH or PUT in my REST API? 【发布时间】:2014-08-06 04:07:42 【问题描述】:

我想为以下场景使用适当的方法设计我的休息端点。

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

我应该把我的终点设计成

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

PATCH /groups/api/v1/groups/group id

with request body like 
action:activate|deactivate

【问题讨论】:

两者都很好。但是请查看 JSON PATCH 格式的 RFC (tools.ietf.org/html/rfc6902)。 PATCH 期望为有效负载获取某种差异/补丁文档(原始 JSON 不是其中之一)。 @JørnWildt 不,PUT 将是一个可怕的选择。你在那儿放什么? PATCH 是唯一明智的选择。好吧,在这种情况下,您可以使用问题中提供的 PATCH 格式,而只需使用 PUT 方法; PUT 示例是错误的。 将一个或多个属性公开为客户端可以通过 PUT 获取和修改的独立资源并没有错。但是,是的,URL 应该是 /groups/api/v1/groups/group id/status 您可以将“活动”或“非活动”或 GET 放入其中以读取当前状态。 这里很好地解释了应该如何真正使用 PATCH:williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot "activate" 不是足够的 RESTful 结构。您可能正在尝试将status 更新为“活动”或“非活动”。在这种情况下,您可以使用正文中的“active”或“deactive”字符串修补到.../status。或者,如果您尝试更新 status.active 的布尔值,您可以使用正文中的布尔值 PATCH 到 .../status/active 【参考方案1】:

PATCH 方法是此处的正确选择,因为您正在更新现有资源 - 组 ID。 PUT 仅应在您替换整个资源时使用。

有关部分资源修改的更多信息,请访问RFC 5789。具体PUT方法描述如下:

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

【讨论】:

公平地说,您可以将字符串 'activate' 或 'deactivate' 放到资源中。因为(似乎)只有一件事可以切换,所以完全替换它并不是什么大不了的事。它确实允许(微不足道的)较小的请求。 请注意,RFC 5789 仍处于提案阶段,尚未被正式接受,目前被标记为“存在错误”。这种“最佳实践”备受争议,从技术上讲,PATCH 还不是 HTTP 标准的一部分。 几年后只需我的 2 美分:您可以将状态本身视为一种资源,如果是这样,对 /status 使用 PUT 在技术上将替换该端点的状态资源。 我敢于反对这些文档,即使它是“最”的 RFC。文档声明您应该使用 PATCH 仅修改资源的一部分,但它忽略了 PATCH 方法被定义为非幂等方法的重要内容。为什么?如果创建 PUT 方法时考虑到整个资源的更新/替换,那么如果 PATCH 方法的目的只是更新资源的一部分,那么为什么不将 PATCH 方法创建为像 PUT 这样的幂等方法?在我看来,更新的幂等性看起来更像是“a=5”(PUT)和“a=a+5”(PATCH)。两者都可以更新整个资源。【参考方案2】:

REST中的R代表资源

(这不是真的,因为它代表 Representational,但记住资源在 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-create 所需的数据相同。

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

【讨论】:

这对我来说非常有用。 “当一个组的“激活”有副作用时,这种模式很有用”-这种模式为什么有用,特别是关于动作何时有副作用,而不是 OP 初始端点 @Abdul,该模式很有用,原因有很多,但有副作用,客户应该很清楚一个动作有什么影响。例如,当 ios 应用决定将整个地址簿作为“联系人”发送时,应该非常清楚创建、更新、删除联系人等的副作用。例如,为了避免大量邮寄所有联系人。 在 RESTfull PUT 中也可以更改实体身份 - 例如可能导致并行请求失败的 PrimaryKey ID。 (例如更新整个实体需要删除一些行并添加新的行,从而创建新实体)PATCH 绝不能这样做,允许无限数量的 PATCH 请求而不影响其他“应用程序” 非常有帮助的答案。谢谢!我还要添加一条评论,就像在 Luke 的回答中一样,指出 PUT/PATCH 之间的区别不仅仅是整体/部分更新,它也是幂等性的不同。这不是一个错误,这是一个有意的决定,我认为在决定 HTTP 方法的使用时,没有多少人会考虑到这一点。 我同意和不同意。 RESTful API 不应反映您的域。他们倾向于对应用程序的用例进行建模,而不是对业务进行建模。 RESTful api 遵循 RFC 2616 是有原因的。作为消费者,我不知道您的业务运营的“副作用”是什么。我所知道的是您的 HTTP VERBS 应该反映对 RESOURCE 的操作。因此,如果 DELETE 是幂等的,则意味着对 RESOURCE 的操作是幂等的。不是“副作用”。发送电子邮件不违反“幂等性”。这是一个商业问题,而不是 RESTful api。【参考方案3】:

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

根据 RFC5789 (https://www.rfc-editor.org/rfc/rfc5789)

现有的 HTTP PUT 方法只允许完全替换 一份文件。该提案增加了一个新的 HTTP 方法 PATCH 来修改 现有的 HTTP 资源。

另外,更详细地说,

PUT 和 PATCH 请求之间的区别体现在 服务器处理封闭实体以修改资源的方式 由 Request-URI 标识。在 PUT 请求中,包含的实体 被认为是存储在上的资源的修改版本 源服务器,并且客户端正在请求存储的版本 被替换。然而,对于 PATCH,封闭的实体包含一个集合 描述资源当前如何驻留在 应修改原始服务器以生成新版本。补丁 方法会影响由 Request-URI 标识的资源,并且它 也可能对其他资源产生副作用;即,新资源 可以通过应用程序创建或修改现有的 补丁。

按照 [RFC2616] 的定义,PATCH 既不安全也不幂等, 部分 9.1.

客户端需要选择何时使用 PATCH 而不是 PUT。对于 例如,如果补丁文档的大小大于 将在 PUT 中使用的新资源数据,那么它可能会生成 感觉使用 PUT 而不是 PATCH。与 POST 的比较甚至更多 困难,因为 POST 的使用方式多种多样,并且可以 如果服务器选择,则包含 PUT 和类似 PATCH 的操作。如果 该操作不会修改请求标识的资源- URI 以可预测的方式,应该考虑 POST 而不是 PATCH 或 PUT。

PATCH 的响应码是

使用 204 响应码是因为响应不携带 消息正文(带有 200 代码的响应将具有)。笔记 也可以使用其他成功代码。

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

警告:实现 PATCH 的 API 必须自动修补。它绝不能 当 GET 请求时,资源可能会被半修补。

【讨论】:

【参考方案4】:

由于您想使用 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 架构风格的 hypermedia as the engine of application state 条件。

【讨论】:

【参考方案5】:

我通常更喜欢简单一点的东西,比如activate/deactivate 子资源(由Link 标头和rel=service 链接)。

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

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

对于消费者来说,这个接口非常简单,它遵循 REST 原则,不会让您将“激活”概念化为单独的资源。

【讨论】:

【参考方案6】:

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

PUT /groups/api/v1/groups/group id/status

    "Status":"Activated"

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

如果需要批量激活/停用,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 建议的想法,但在具体实现上略有变化。

【讨论】:

以上是关于我应该在我的 REST API 中使用 PATCH 还是 PUT?的主要内容,如果未能解决你的问题,请参考以下文章

REST API PATCH或PUT

我应该在我的 REST Api 中使用 Helmet 的哪些模块

在 REST API 现实生活场景中使用 PUT 与 PATCH 方法

Mailchimp API Put 或 Patch 使用 Delphi REST

REST API PATCH 请求

节点 express REST API 中的 CORS 错误(PATCH 请求)