如何为非 CRUD“命令”设计 REST API,例如激活和停用资源?
Posted
技术标签:
【中文标题】如何为非 CRUD“命令”设计 REST API,例如激活和停用资源?【英文标题】:How to design REST API for non-CRUD "commands" like activate and deactivate of a resource? 【发布时间】:2014-03-14 17:20:02 【问题描述】:在我决定提出这个问题之前,我已经搜索了很长时间的答案,但我没有找到任何令人满意的答案。 (例如Examples of the best SOAP/REST/RPC web APIs? And why do you like them? And what's wrong with them?)
问题其实很简单。我有一个名为 Account 的对象/资源。我的 REST API 支持所有带有 GET、POST、PUT 和 DELETE 的 CRUD,并具有适当的错误处理、状态代码等。
此外,我还想公开一个 API(“命令”)来激活和停用选定的帐户资源。 即使“isActive”是帐户的属性,我也不想只使用整个帐户的 CRUD 中的更新。
我知道这样的设计很容易违反 REST 原则并进行 RPC 风格设计:
PUT /api/account/:accountId/activate
PUT /api/account/:accountId/deactivate
那么这个用例的最佳解决方案是什么?
我目前的想法是使用像这样的 PUT 和 DELETE 动词(将其视为子资源),正如这里提出的 http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api#restful:
PUT /api/account/:accountId/isActive // 用于激活
DELETE /api/account/:accountId/isActive // 用于停用
你有什么解决方案?
【问题讨论】:
我不认为这是一个“RPC”设计。这是一个基于“消息”的设计。虽然,为什么不在第一个例子中 POST 呢? 使用 POST 作为非幂等方法似乎是标准的。我仍然不确定这种“命令”案例的最佳设计模式是什么。 我发现这个关于“REST-Ful API 设计”youtu.be/oG2rotiGr90 的有趣讨论并使用它定义的规则,例如PUT/PATCH 用于“激活”、“打开”、“安装”等操作。 【参考方案1】:想出一个你想要修改的特性的名词怎么样 - 在这个例子中是“状态”。这将成为父实体的子资源。因此,对于您的情况,我将按如下方式对 URI 进行建模:
/api/accounts/accountId/status
如果“更新”语义是幂等的,那么 PUT 将是最合适的,否则就需要是 POST(例如,如果涉及 nonce 并且被服务无效)。实际的有效负载将包含新状态的描述符。
请注意,我将“帐户”设为复数,因为您可以拥有多个帐户,但状态是单数,因为您的帐户只能有一个状态。
【讨论】:
有趣的方法。然而,通过这种方式,我们将从功能的角度引入一些新的东西(一个新的属性?),从而增加了复杂性。然而,正如我在设计核心领域模型时所说的直觉。 它的旧线程但 PUT“应该”有正文【参考方案2】:PATCH 在这种情况下是最合适的方法。更多信息请访问RESTful URL for "Activate"
【讨论】:
这种方法的问题是它隐藏了请求正文中的语义。即使从纯粹设计的角度来看它可能是正确的,我也不会这样做。【参考方案3】:POST 方法将创建资源“帐户”。活动可以看作是资源“帐户”的属性之一。因此它应该是一个 PUT 请求。
我会说即使停用也必须是 PUT 请求,因为帐户资源仍然存在。
要激活帐户,您可以在资源上设置属性。那就是:
/api/account/accountId?activate=true
停用:
/api/account/accountId?activate=false
对帐户的 GET 请求将返回一个带有激活值的 JSON。
DELETE 请求应完全删除帐户资源。
【讨论】:
我很困惑为什么这个答案被否决了。我以前见过这个,它看起来很合适。 Neerja,我认为不愿意以这种方式使用查询 (?) 参数,而是将其限制为选择标准/过滤器。 我们应该将查询参数限制为过滤 我认为这不是一个好的答案。首先,PUT 应该“用请求有效负载替换目标资源的所有当前表示”(tools.ietf.org/html/rfc7231#section-4.1),这意味着您应该 PUT 所有有效负载,而不仅仅是单个status
字段。你最好建议 PATCH。
其次。 “GET、HEAD、OPTIONS 和 TRACE 方法被定义为安全的”(tools.ietf.org/html/rfc7231#section-4.2.1),这意味着用户不必太在意发送它们 - 他应该确信他对 GET 请求的操作不会造成任何伤害。使用?activate=false
参数请求会造成伤害。相比之下,POST、PUT、PATCH、DELETE 并不安全,当用户发送它们时,他必须认为他应对其造成的任何伤害负责。【参考方案4】:
首先,PUT
与POST
相比更合适,因为您正在为已知位置创建资源。而且,我认为,DELETE
没有两难选择。因此,乍一看,您当前的方法似乎优于其他方法。
我以前也是这么想的,直到我实现了自己的 REST api,我希望管理员能够将帐户设置为停用 - 但不删除,只是“禁止” - 状态。当我仔细考虑之后,我决定这样做反之亦然。
让我解释一下。我喜欢将activation
资源视为“激活帐户的选项”。因此,如果存在/account/foo/activation
之类的网址,则只能表示该帐户未激活并且用户有权激活它。如果不存在,则说明该帐户已被激活或处于禁止状态。
因此,为了激活帐户,唯一合理的做法是尝试DELETE
资源。而且,为了启用激活,管理员必须PUT
激活资源。
现在,我想到的问题是如何区分被禁止的帐户和已激活的帐户。但由于禁令也可以被视为一种资源,您可以创建一个/account/foo/ban
资源集合。为了封禁一个帐户,可能在固定的时间内,您只需 POST
该集合下的一个资源,其中包含封禁的所有详细信息。
【讨论】:
以上是关于如何为非 CRUD“命令”设计 REST API,例如激活和停用资源?的主要内容,如果未能解决你的问题,请参考以下文章
如何为 django-rest-framework api 编写单元测试?
Django Rest Framework api如何为所有人添加身份验证权限
如何为 WCF REST API 生成提到的 json 格式