【问题标题】:REST endpoint for complex actions用于复杂操作的 REST 端点
【发布时间】:2020-02-28 18:59:26
【问题描述】:

我有一个 REST API,它将数据库中的数据提供给前端 React 应用程序和 Android 应用程序。 API 对每个模型都有多个公共端点:
- GET /model/<id> 检索单个对象
- POST /model 创建
- PATCH /model/<id> 更新单个模型
- GET /model 列出对象
- DELETE /model/<id> 删除对象

目前我正在开发一个 Android 应用程序,我发现这样的方案可以让我对 API 执行许多额外的请求。例如,每个 Order 对象都有一个 user_creator 条目。所以,如果我想删除指定用户创建的所有订单,我需要
1) 列出所有用户GET /user
2) 选择我需要的
3) 列出他创建的所有订单GET /order?user=user_id
4) 选择我要删除的订单
5) 删除订单DELETE /order/<id>

我想知道添加几个端点是否可以,例如GET /order/delete?user=user_id。通过这样做,我可以摆脱动作 4 和 5。所有过滤都将在后端完成。然而,在我看来,这是一个糟糕的架构解决方案,因为我之前使用的所有 API 都没有这样的方法,而且所有过滤、排序和其他“美化”的东西通常都在 API 用户端,而不是后端。

在您的回答中,请提供您认为对该问题最好的解决方案,并至少简要说明您的观点,以便我从中学习

【问题讨论】:

    标签: rest model architecture request endpoint


    【解决方案1】:

    孤立地解决你的问题:

    • 您有一个 Order 集合和一个 User 集合
    • 用户 1..* 订单
    • 您要删除给定用户 ID 的所有订单

    我会使用以下 URI:

    // delete all orders for a given user
    POST /users/:id/orders/delete
    

    当然,这显示了用户和订单之间的关系,并且不言自明,您只处理与特定用户关联的订单。此外,鉴于该操作将导致服务器上的副作用,那么您应该 POST 而不是 GET(读取资源不应该更改服务器)。相同的逻辑可用于创建仅用于拉取用户订单的端点,例如

    // get all orders for a given user
    GET /users/:id/orders
    

    【讨论】:

      【解决方案2】:

      HTTP 的应用领域是通过网络传输文档。您的“REST API”是一个外观,其作用类似于文档存储,并作为传输文档的副作用执行有用的工作。见Jim Webber (2011)

      所以基本的习惯用法是我们发布一个文档,或者我们发送一堆对现有文档的编辑,然后服务器解释这些更改并做一些有用的事情。

      所以一个基于现有远程创作语义的简单协议可能看起来像

      GET /orders?user=user_id
      
      Make local edits to the representation of that list provided by the server
      
      PUT /orders?user=user_id
      

      如何做到这一点的语义是交换双方都需要理解的。也许您从列表中删除不需要的项目?也许列表中的每条记录都有一个状态条目,而您将状态从活动更改为过期。

      在网络上,我们倾向于使用表单提交来代替远程创作语义。您从某个地方得到一张空白表格,您自己填写,然后将其发布到指定的收件箱,然后由负责处理该收件箱的人员完成工作。

      因此,我们将一个空白表单加载到浏览器中,然后对其进行更改,然后将其发布到表单中列出的资源中。

      GET /the-blank-form?user=user_id
      
      Make changes in the form...
      
      POST ????
      

      target-uri 应该是什么?网络浏览器不在乎;它只是将表单提交给它收到的表示指定的任何目标。一个答案可能是直接把它寄回我们得到它的地方:

      POST /the-blank-form?user=user_id
      

      而且效果很好(只要您正确管理元数据)。另一种可能性是将更改发送到您希望反映这些更改的资源:

      POST /orders?user=user_id
      

      事实证明 也可以正常工作。 HTTP 在规范中内置了有趣的cache invalidation 语义,因此我们可以通过使用与 POST 调用目标相同的资源来确保客户端的陈旧副本或订单集合资源无效。

      目前我的 API 满足 REST 底部的表格,因此,任何额外的端点都会破坏它。会不会致命,这是个问题。

      不,没关系 - 只需在适当的资源上添加/扩展一个 POST 处理程序来处理新语义。

      更长的答案:维基百科中的表格很好地代表了常见做法;但常见的做法并不完全正确。部分问题在于 REST 包含一个统一接口。除此之外,这意味着所有资源都理解相同的消息语义。 REST 中不存在“集合资源”与“成员资源”的概念——两者的语义相同。

      另一种说法是通用组件永远不知道它正在与之通信的资源是集合还是成员。所有不安全的方法(POST/PUT/PATCH/DELETE/etc)都隐含了目标uri表示的invalidation

      现在 POST 碰巧意味着“做一些尚未标准化的事情”——参见 Fielding 2009。这是语义约束最少的方法。

      POST 方法请求目标资源根据资源自己的特定语义处理请求中包含的表示。 -- RFC 7231

      POST 处理程序可以根据请求负载的内容进行分支;如果你看到 X,创建一些东西,如果你看到 Y 删除其他东西。这类似于有两个不同的 Web 表单,具有不同的语义,它们提交到同一个目标资源。

      【讨论】:

      • 好吧,我想你想说的是数据服务器发送给客户端和向后的变化可能因情况而异,后端所做的更改也可能不同。这是我要使用的好点。但是,更具体地说,我的问题是是否可以向一个模型添加一个额外的端点而不将其添加到其他模型。目前我的 API 满足 REST 底部的表格,因此,任何额外的端点都会破坏它。会不会死,这是个问题。如果是,解决方法是什么?
      • 我不得不否决这个答案,完全不清楚重点是什么以及它与 REST 最佳实践的关系。我感觉答案的意思是“做你想做/需要做的事,不要担心”,但这背后的原因完全不清楚。
      猜你喜欢
      • 1970-01-01
      • 2019-10-19
      • 2011-04-20
      • 2021-09-17
      • 1970-01-01
      • 1970-01-01
      • 2022-01-13
      • 2016-01-04
      • 2016-11-17
      相关资源
      最近更新 更多