我想为以下场景使用适当的方法设计我的休息端点。
有一个群。每个组都有一个状态。该组可以由管理员激活或停用。
我应该将终点设计为
PUT /groups/api/v1/groups/{group id}/status/activate
或者
PATCH /groups/api/v1/groups/{group id}
with request body like
{action:activate|deactivate}
activate
”不是充分的 RESTful 结构。您可能正在尝试将 status
更新为“活动”或“停用”。在这种情况下,您可以使用正文中的“active”或“deactive”字符串修补到 .../status
。或者,如果您尝试更新 status.active
处的布尔值,您可以使用正文中的布尔值 PATCH 到 .../status/active
当您更新现有资源 - 组 ID 时,PATCH
方法是正确的选择。仅当您替换整个资源时才应使用 PUT
。
RFC 5789 中提供了有关部分资源修改的更多信息。具体而言,PUT
方法描述如下:
一些扩展超文本传输协议 (HTTP) 的应用程序需要一项功能来进行部分资源修改。现有的 HTTP PUT 方法只允许完全替换文档。该提案添加了一个新的 HTTP 方法 PATCH,以修改现有的 HTTP 资源。
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
。
我建议使用 PATCH,因为您的资源“组”有很多属性,但在这种情况下,您只更新激活字段(部分修改)
根据 RFC5789 (https://www.rfc-editor.org/rfc/rfc5789)
现有的 HTTP PUT 方法只允许完全替换文档。该提案添加了一个新的 HTTP 方法 PATCH,以修改现有的 HTTP 资源。
此外,更详细地说,
PUT 和 PATCH 请求之间的区别体现在服务器处理封闭实体以修改由 Request-URI 标识的资源的方式上。在 PUT 请求中,包含的实体被认为是存储在源服务器上的资源的修改版本,并且客户端请求替换存储的版本。然而,对于 PATCH,封闭的实体包含一组指令,描述如何修改当前驻留在源服务器上的资源以生成新版本。 PATCH 方法会影响 Request-URI 标识的资源,也可能对其他资源产生副作用;即,可以通过应用 PATCH 创建新资源或修改现有资源。正如 [RFC2616] 第 9.1 节所定义的那样,PATCH 既不安全也不幂等。
客户端需要选择何时使用 PATCH 而不是 PUT。例如,如果补丁文档的大小大于将在 PUT 中使用的新资源数据的大小,那么使用 PUT 而不是 PATCH 可能是有意义的。与 POST 的比较更加困难,因为 POST 的使用方式多种多样,如果服务器选择,它可以包含 PUT 和类似 PATCH 的操作。如果操作没有以可预测的方式修改由 Request-URI 标识的资源,则应考虑使用 POST 而不是 PATCH 或 PUT。
PATCH 的响应代码是
使用 204 响应代码是因为响应不包含消息正文(带有 200 代码的响应将具有)。请注意,也可以使用其他成功代码。
另请参阅 thttp://restcookbook.com/HTTP%20Methods/patch/
警告:实现 PATCH 的 API 必须自动修补。当 GET 请求时,资源不能被半修补。
由于您想使用 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 条件。
实现这种行为的一种可能选择是
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 建议的想法,但在具体实现上略有变化。
我通常更喜欢更简单的东西,例如 activate
/deactivate
子资源(由 Link
标头与 rel=service
链接)。
POST /groups/api/v1/groups/{group id}/activate
或者
POST /groups/api/v1/groups/{group id}/deactivate
对于消费者来说,这个接口非常简单,它遵循 REST 原则,不会让您将“激活”概念化为单独的资源。