在REST中需要更多动词时该怎么办

时间:2010-04-22 02:32:46

标签: web-services rest

我的另一个类似的question,但讨论偏离了我正在讨论的问题。

假设我有一个处理费用报告(ER)的系统。您可以创建和编辑它们,添加附件以及批准/拒绝它们。

费用报告可能如下所示:

GET /er/1
=>
{"title": "Trip to NY", "totalcost": "400 USD",
 "comments": [
   "john: Please add the total cost",
   "mike: done, can you approve it now?"
   ],
 "approvals": [
   {"john": "Pending"}, {"finance-group": "Pending"}]
}

看起来很好,对吗?这是费用报告文件的样子。

如果您想要更新它,您可以这样做:

POST /er/1
{"title": "Trip to NY 2010"}

如果您要批准,可以这样做:

POST /er/1/approval
{"approved": true}

但是,如果您想要同时更新报告批准该怎么办?我们怎么做?如果您只想批准,那么对POST这样的/er/1/approval进行操作是有意义的。

我们可以在URL POST /er/1?approve=1中放置一个标志,并将数据更改作为正文发送,但该标志似乎不是RESTful。

我们也可以提交特殊领域,但这似乎也有点hacky。如果我们这样做,那么为什么不发送包含set_titleadd_to_cost等属性的数据?

我们可以创建一个用于更新和批准的新资源,但是(1)我无法想到如何在没有动词的情况下命名它,以及(2)根据什么动作可以命名资源似乎是不对的要完成它(如果我们添加更多动作会发生什么?)

我们可以有一个X-Approve:True | False标题,但标题似乎是错误的工具。如果不在浏览器中使用javascript,也很难获得设置的标题。

我们可以使用自定义媒体类型application/approve+yes,但这似乎并不比创建新资源更好。

我们可以创建一个临时的“批处理操作”网址/er/1/batch/A。然后,客户端发送多个请求,可能POST /er/1/batch/A进行更新,然后POST /er/1/batch/A/approval批准,然后POST /er/1/batch/A/status结束批处理。在后端,服务器在某处将所有批处理请求排队,然后在收到“结束批处理”请求时在同一后端事务中处理它们。显然,它的缺点是它引入了很多复杂性。

那么,解决在单个请求中执行多个操作的问题的一般方法是什么?一般是因为很容易想象可能在同一请求中执行的其他操作:

  1. 禁止或发送通知(发送电子邮件,聊天,其他系统,等等)
  2. 覆盖一些验证(最高费用,晚宴参与者姓名)
  3. 触发文档中没有表示的后端工作流程。
  4. 这也是一个表现问题。 HTTP呼叫到达网络(如果你有高延迟或连接不稳定可能会出现问题),所以你可以做的越多越好。

4 个答案:

答案 0 :(得分:23)

REST架构表示资源由服务器管理并由URL标识。

在那种情况下,/er/1/approval不是一个合理的URL或模型,除非您拥有在服务器端管理和操作的批准对象或实体。在我看来,实体是费用报告本身,这意味着,/er/1是您的URL路径。

现在,对于动词......你可以向你发送(POST)你喜欢的任何消息。

设置数据:

{ action: "modify", data: { purpose : "Club hopping" } }

批准:

{ action: "approve" }

添加项目:

{ action:"additem", data: { amount:72.13, category:113, note:"client dinner" }}

等。


来自Fielding's Ch5, which defined REST

  

(参数请求)的参数包括请求控制数据,指示请求目标的资源标识符和可选表示。

...和...

  

控制数据定义组件之间消息的目的,,例如请求的操作或响应的含义。它还用于参数化请求并覆盖某些连接元素的默认行为。例如,可以通过请求或响应消息中包含的控制数据来修改缓存行为。


因此,如果您想对资源执行多个操作,那么您应该在“控制数据”中嵌入多个消息或操作请求。在我的示例中,发布的数据类似于:

{ action: "modify", data: { purpose : "Club hopping" } }
{ action: "approve" }

但你可能想要概括它,以便:

{ actions: [ {action:"modify", data: {...} }, { action:"approve"} ] } 

您的服务器可以在每种特定类型的实体上处理的消息或操作由您来定义。

ps:有时REST实现使用HTTP PUT来创建资源,使用POST来修改或处理现有资源。

和:我喜欢这篇文章How to GET a cup of coffee

答案 1 :(得分:4)

为了操纵资源的状态,我经常喜欢使用“状态存储桶”。我们的想法是,当您将一个对象“添加”到该存储桶中时,它就会获得该状态。就像桌子上有进出箱子一样。文档的位置定义了它的状态。

所以,你可以做一些简单的事情:

POST /Expenses/Approved
{ .. Expense document ... }

或者您在文档中暗示的更复杂的案例,其中有多人必须批准该文档。

POST /ExpenseApprover/John/ApprovedExpenses
{ .. Expense document ... }

如果您需要提交费用报告以供批准,您可以

POST /ExpenseApprover/John/Pending
{ .. Expense document ... }

不要忘记超媒体可以启用此流程工作流程。想象一下有人创建了一个初始费用报告,服务器可以使用以下JSON进行响应。

{ "id" : "234",
  "title": "Trip to NY", "totalcost": "400 USD",
  "submit_url": "/ExpenseApprover/John/Pending"
}

客户端可以POST到submit_url,将费用转移到下一步。然后,当约翰检索费用时,他得到了

{ "id" : "234",
  "title": "Trip to NY", "totalcost": "400 USD",
  "approve_url": "/ExpenseApprover/Finance/Pending",
  "denied_url": "/ExpenseApprover/John/Denied",
}

财务部门做的时候

GET /ExpenseApprover/Finance/Pending

他们可以获得待处理费用清单

{ PendingExpense: [
    { "id" : "234",
      "title": "Trip to NY", "totalcost": "400 USD",
     "approve_url": "/Expense/Approved",
     "denied_url": "/ExpenseApprover/Finance/Denied",
    }
   ]
}

请原谅我可怕的JSON,但我希望您能够了解在响应中包含链接,您可以指导应用程序的流程。你也可以不再担心网址的样子,因为客户并不在乎。客户端根据属性名称从响应中读取URL并解除引用。您可以根据最佳网址结构改变主意一百万次,并且您的客户不会受到影响。只是不要更改属性名称!

这些“状态存储桶”网址用于保存一组具有类似状态的资源。我们的想法是将文档发布到集合中:

POST /ExpenseApprover/Finance/Denied

{"id" : "234", "title": "Trip to NY", "totalcost": "400 USD"}

没有必要唯一地定义您在URL中添加的特定费用,因为正文文档应包含某种识别键值。
这种技术对于标记费用有差异同样有效。您只需创建一个新资源,其中包含存在差异的费用,并将费用报告发布到该资源中。

POST /Discrepancies
{"id" : "234", "title": "Trip to NY", "totalcost": "400 USD"}

答案 2 :(得分:2)

  

在REST中需要更多动词时该怎么办

您有3个选项:

  • 创建一个新资源并使用可用的HTTP方法来描述您想要的内容
  • 检查标准,也许你错过了现有的方法
  • 发送一个关于您所需方法的新RFC,以便他们接受它

在你的情况下,你错过了RFC of the PATCH method一两个月。

答案 3 :(得分:1)

我认为你要让它变得更加复杂。将您的费用报告视为完整资源,对其进行任何编辑只需将新表示形式提供给资源所在的URI。无需自定义操作来更改状态,只需获取资源 - 进行编辑 - 然后将其恢复。完成。