我正在为客户管理系统编写RESTful服务,我正在尝试找到部分更新记录的最佳实践.例如,我希望调用者能够使用GET请求读取完整记录.但是为了更新它,只允许记录上的某些操作,比如将状态从ENABLED更改为DISABLED.(我有比这更复杂的场景)
出于安全原因,我不希望调用者仅使用更新的字段提交整个记录(这也感觉有点过分).
是否有推荐的构建URI的方法?在阅读REST书籍时,RPC样式调用似乎不受欢迎.
如果以下调用返回ID为123的客户的完整客户记录
GET /customer/123{lots of attributes} ENABLED {even more attributes}
我该如何更新状态?
POST /customer/123/statusDISABLED 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
谢谢弗兰克
你基本上有两个选择:
使用PATCH
(但请注意,您必须定义自己的媒体类型,以指定将要发生的事情)
使用POST
子资源并返回303 See Other,Location标头指向主资源.303的目的是告诉客户端:"我已经执行了您的POST,结果是其他资源已更新.请参阅位置标头,该资源是哪个." POST/303用于迭代添加资源以构建某些主要资源的状态,它非常适合部分更新.
您应该使用POST进行部分更新.
要更新客户123的字段,请对/ customer/123进行POST.
如果您只想更新状态,您还可以PUT到/ customer/123/status.
通常,GET请求不应有任何副作用,PUT用于写入/替换整个资源.
这直接来自HTTP,如下所示:http://en.wikipedia.org/wiki/HTTP_PUT#Request_methods
您应该使用补丁部分更新-无论是使用JSON-补丁文件(见http://tools.ietf.org/html/draft-ietf-appsawg-json-patch-08或http://www.mnot.net/ blog/2012/09/05/patch)或XML补丁框架(参见http://tools.ietf.org/html/rfc5261).在我看来,json-patch最适合您的业务数据.
使用JSON/XML补丁文档的PATCH具有非常严格的前向语义以进行部分更新.如果你开始使用POST,与原文件的修改后的副本,对部分更新你很快就会碰到您想缺失值(或者说,空值)问题表示无论是"忽略此属性"或"此属性设置为空值" - 这导致了一个被黑客入侵的解决方案的兔子洞,最终将导致你自己的补丁格式.
你可以找到一个更深入的答案在这里:http://soabits.blogspot.dk/2013/01/http-put-patch-or-post-partial-updates.html.
我遇到了类似的问题.当您只想更新单个字段时,子资源上的PUT似乎有效.但是,有时您需要更新一堆内容:考虑表示资源的Web表单,并选择更改某些条目.用户提交表单不应导致多个PUT.
以下是我能想到的两种解决方案:
用整个资源做一个PUT.在服务器端,定义具有整个资源的PUT忽略所有未更改的值的语义.
使用部分资源执行PUT.在服务器端,将其语义定义为合并.
2只是1的带宽优化.如果资源定义某些字段是必需字段(想想原型缓冲区),有时1是唯一选项.
这两种方法的问题在于如何清除字段.您将不得不定义一个特殊的空值(特别是对于原型缓冲区,因为没有为原型缓冲区定义空值),这将导致字段清除.
评论?
要添加到增强问题的内容.我认为您通常可以完美地设计更复杂的业务操作.但是你必须放弃思考的方法/程序风格,并在资源和动词中思考更多.
邮件发送
POST /customers/123/mails
payload:
{from: x@x.com, subject: "foo", to: y@y.com}
然后,此资源+ POST的实现将发送邮件.如果有必要,您可以提供类似/ customer/123/outbox的内容,然后提供/ customer/mails/{mailId}的资源链接.
客户数量您可以像搜索资源一样处理它(包括带有分页和num-found信息的搜索元数据,它可以为您提供客户数量).
GET /customers
response payload:
{numFound: 1234, paging: {self:..., next:..., previous:...} customer: { ...} ....}
为了修改状态,我认为RESTful方法是使用描述资源状态的逻辑子资源。当您减少一组状态时,此IMO非常有用且干净。它使您的API更具表现力,而无需强制执行客户资源的现有操作。
例:
POST /customer/active <-- Providing entity in the body a new customer { ... // attributes here except status }
POST服务应返回ID为:
{ id:123, ... // the other fields here }
所创建资源的GET将使用资源位置:
GET /customer/123/active
GET / customer / 123 / inactive应该返回404
对于PUT操作,无需提供Json实体,它将仅更新状态
PUT /customer/123/inactive <-- Deactivating an existing customer
提供实体将使您可以更新客户的内容并同时更新状态。
PUT /customer/123/inactive { ... // entity fields here except id and status }
您正在为客户资源创建概念性子资源。这也与Roy Fielding对资源的定义是一致的:“ ...资源是对一组实体的概念性映射,而不是在任何特定时间点对应于该映射的实体...”在这种情况下,概念映射是活动客户到状态为ACTIVE的客户。
读取操作:
GET /customer/123/active GET /customer/123/inactive
如果您在一个呼叫之后又一个呼叫必须立即返回状态404,则成功的输出可能不包含该状态,因为它是隐式的。当然,您仍然可以使用GET / customer / 123?status = ACTIVE | INACTIVE直接查询客户资源。
DELETE操作很有趣,因为语义可能会造成混淆。但是,您可以选择不为该概念性资源发布该操作,或者根据您的业务逻辑使用该操作。
DELETE /customer/123/active
这样一来,您的客户就可以进入已删除/已禁用状态或处于相反状态(活动/无效)。