如何为非 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】:

首先,PUTPOST 相比更合适,因为您正在为已知位置创建资源。而且,我认为,DELETE 没有两难选择。因此,乍一看,您当前的方法似乎优于其他方法。

我以前也是这么想的,直到我实现了自己的 REST api,我希望管理员能够将帐户设置为停用 - 但不删除,只是“禁止” - 状态。当我仔细考虑之后,我决定这样做反之亦然

让我解释一下。我喜欢将activation 资源视为“激活帐户的选项”。因此,如果存在/account/foo/activation 之类的网址,则只能表示该帐户未激活并且用户有权激活它。如果不存在,则说明该帐户已被激活处于禁止状态。

因此,为了激活帐户,唯一合理的做法是尝试DELETE 资源。而且,为了启用激活,管理员必须PUT 激活资源。

现在,我想到的问题是如何区分被禁止的帐户和已激活的帐户。但由于禁令也可以被视为一种资源,您可以创建一个/account/foo/ban 资源集合。为了封禁一个帐户,可能在固定的时间内,您只需 POST 该集合下的一个资源,其中包含封禁的所有详细信息。

【讨论】:

以上是关于如何为非 CRUD“命令”设计 REST API,例如激活和停用资源?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 django-rest-framework api 编写单元测试?

如何为 Node JS 设置 REST API

Django Rest Framework api如何为所有人添加身份验证权限

如何为 WCF REST API 生成提到的 json 格式

如何为 REST API 构建 Mongo 数据库 [关闭]

如何为 Laravel 模块架构应用 CRUD 生成器?