我正在为客户管理系统编写一个RESTful服务,并试图找到部分更新记录的最佳实践。例如,我希望调用者能够使用GET请求读取完整的记录。但是为了更新它,只允许对记录进行某些操作,比如将状态从ENABLED更改为DISABLED。(我有比这更复杂的场景)

出于安全原因,我不希望调用者只提交更新字段的整个记录(这也感觉有点过分)。

是否有构造uri的推荐方法?在阅读REST书籍时,RPC样式的调用似乎是不受欢迎的。

如果下面的调用返回id为123的客户的完整客户记录

GET /customer/123
<customer>
    {lots of attributes}
    <status>ENABLED</status>
    {even more attributes}
</customer>

我该如何更新状态?

POST /customer/123/status
<status>DISABLED</status>

POST /customer/123/changeStatus
DISABLED

...

更新:扩充问题。如何将“业务逻辑调用”合并到REST api中?有没有大家都同意的做法?并非所有的方法本质上都是CRUD。有些更复杂,如“sendEmailToCustomer(123)”,“mergeCustomers(123, 456)”,“countCustomers()”

POST /customer/123?cmd=sendEmail

POST /cmd/sendEmail?customerId=123

GET /customer/count 

当前回答

查看http://www.odata.org/

它定义了MERGE方法,所以在你的例子中,它是这样的:

MERGE /customer/123

<customer>
   <status>DISABLED</status>
</customer>

只有status属性被更新,其他值被保留。

其他回答

你的扩充问题需要补充的东西。我认为您通常可以完美地设计更复杂的业务操作。但你必须放弃方法/过程的思维方式,更多地考虑资源和动词。

邮件发送


POST /customers/123/mails

payload:
{from: x@x.com, subject: "foo", to: y@y.com}

这个资源+ POST的实现将发送邮件。如果有必要,你可以提供类似/customer/123/发件箱这样的东西,然后提供到/customer/mails/{mailId}的资源链接。

客户数

您可以像处理搜索资源一样处理它(包括具有分页和num-found信息的搜索元数据,它可以提供客户的数量)。


GET /customers

response payload:
{numFound: 1234, paging: {self:..., next:..., previous:...} customer: { ...} ....}

查看http://www.odata.org/

它定义了MERGE方法,所以在你的例子中,它是这样的:

MERGE /customer/123

<customer>
   <status>DISABLED</status>
</customer>

只有status属性被更新,其他值被保留。

没关系。就REST而言,你不能做GET,因为它不是可缓存的,但不管你使用POST, PATCH, PUT或其他什么都没关系,URL看起来是什么也没关系。如果您正在使用REST,那么重要的是当您从服务器获得资源的表示时,该表示能够提供客户端状态转换选项。

如果您的GET响应有状态转换,客户机只需要知道如何读取它们,服务器可以根据需要更改它们。这里的更新是使用POST完成的,但是如果它被更改为PATCH,或者如果URL更改了,客户端仍然知道如何进行更新:

{
  "customer" :
  {
  },
  "operations":
  [
    "update" : 
    {
      "method": "POST",
      "href": "https://server/customer/123/"
    }]
}

你可以列出必要的/可选的参数,让客户端反馈给你。这取决于应用程序。

就业务操作而言,这可能是与客户资源链接的不同资源。如果你想发送一封电子邮件给客户,也许该服务是它自己的资源,你可以POST,所以你可以在客户资源中包括以下操作:

"email":
{
  "method": "POST",
  "href": "http://server/emailservice/send?customer=1234"
}

以下是演示者REST架构的一些优秀视频和示例。Stormpath只使用GET/POST/DELETE,这很好,因为REST与你使用的操作或url的外观没有任何关系(除了GET应该是可缓存的):

https://www.youtube.com/watch?v=pspy1H6A3FM, https://www.youtube.com/watch?v=5WXYw4J4QOU, http://docs.stormpath.com/rest/quickstart/

你基本上有两个选择:

Use PATCH (but note that you have to define your own media type that specifies what will happen exactly) Use POST to a sub resource and return 303 See Other with the Location header pointing to the main resource. The intention of the 303 is to tell the client: "I have performed your POST and the effect was that some other resource was updated. See Location header for which resource that was." POST/303 is intended for iterative additions to a resources to build up the state of some main resource and it is a perfect fit for partial updates.

您应该使用PATCH进行部分更新—使用json补丁文档(参见https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-patch-08或http://www.mnot.net/blog/2012/09/05/patch)或XML补丁框架(参见https://www.rfc-editor.org/rfc/rfc5261)。但在我看来,json-patch最适合您的业务数据类型。

带有JSON/XML补丁文档的PATCH对于局部更新具有非常严格的前向语义。如果您开始使用POST,使用原始文档的修改副本,进行部分更新,您很快就会遇到问题,您希望丢失值(或者更准确地说,空值)来表示“忽略此属性”或“将此属性设置为空值”—这导致了一个被破解的解决方案的兔子洞,最终将导致您自己的补丁格式。

你可以在这里找到更深入的答案:http://soabits.blogspot.dk/2013/01/http-put-patch-or-post-partial-updates.html。