我正在为客户管理系统编写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
...
Update :增加问题 . 如何将'business logic calls'纳入REST API?这是否有商定的方式?并非所有方法都是CRUD本质上 . 有些更复杂,比如'sendEmailToCustomer(123)', ' mergeCustomers(123,456)', ' countCustomers()'
POST /customer/123?cmd=sendEmail
POST /cmd/sendEmail?customerId=123
GET /customer/count
谢谢弗兰克
10 回答
你基本上有两个选择:
使用
PATCH
(但请注意,您必须定义自己的媒体类型,以指定将要发生的事情)对子资源使用
POST
并返回303 See Other,Location Headers 指向主资源 . 303的目的是告诉客户端:"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用于迭代添加资源以构建某些主要资源的状态,它非常适合部分更新 .您应该使用POST进行部分更新 .
要更新客户123的字段,请对/ customer / 123进行POST .
如果您只想更新状态,您还可以PUT到/ customer / 123 / status .
通常,GET请求不应有任何副作用,PUT用于写入/替换整个资源 .
这直接来自HTTP,如下所示:http://en.wikipedia.org/wiki/HTTP_PUT#Request_methods
您应该使用PATCH进行部分更新 - 使用json-patch文档(请参阅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的实现将发送邮件 . 如果有必要,您可以提供类似/ customer / 123 / outbox的内容,然后提供/ customer / mails / 的资源链接 .
客户数量
您可以像搜索资源一样处理它(包括带有分页和num-found信息的搜索元数据,它可以为您提供客户数量) .
为了修改状态,我认为RESTful方法是使用描述资源状态的逻辑子资源 . 当您拥有减少的状态集时,此IMO非常有用且干净 . 它使您的API更具表现力,而不会强制您的客户资源的现有操作 .
例:
POST服务应返回具有id的新创建的客户:
创建的资源的GET将使用资源位置:
一个GET/ customer / 123 / inactive应返回404
对于PUT操作,在不提供Json实体的情况下,它只会更新状态
提供实体将允许您更新客户的内容并同时更新状态 .
您正在为客户资源创建概念性子资源 . 这也与Roy Fielding对资源的定义一致:“......资源是对一组实体的概念映射,而不是与任何特定时间点的映射相对应的实体......”在这种情况下概念映射是活动的 - 客户到状态= ACTIVE的客户 .
读操作:
如果您在另一个呼叫返回状态404之后立即进行这些呼叫,则成功输出可能不包括隐含的状态 . 当然,您仍然可以使用GET / customer / 123?status = ACTIVE | INACTIVE直接查询客户资源 .
DELETE操作很有意思,因为语义可能令人困惑 . 但是您可以选择不为此概念资源发布该操作,或者根据您的业务逻辑使用它 .
那个可以让您的客户处于DELETED / DISABLED状态或相反状态(ACTIVE / INACTIVE) .
使用PUT更新不完整/部分资源 .
您可以接受jObject作为参数并解析其值以更新资源 .
以下是您可以用作参考的功能:
退房http://www.odata.org/
它定义了MERGE方法,所以在你的情况下它会是这样的:
仅更新
status
属性,并保留其他值 .关于你的更新 .
我认为CRUD的概念在API设计上引起了一些混乱 . CRUD是用于对数据执行的基本操作的一般低级概念,而HTTP谓词只是可能或可能不映射到CRUD操作的请求方法(created 21 years ago) . 实际上,尝试在HTTP 1.0 / 1.1规范中找到CRUD首字母缩略词的存在 .
可以在Google cloud platform API documentation中找到应用实用惯例的非常好解释的指南 . 它描述了创建基于资源的API背后的概念,它强调了大量的资源而不是操作,并包括您正在描述的用例 . 虽然这只是他们产品的常规设计,但我觉得它很有意义 .
这里的基本概念(以及产生很多混淆的概念)是"methods"和HTTP动词之间的映射 . 有一件事是定义你的API将对哪些类型的资源做什么"operations"(方法)(例如,获取客户列表或发送电子邮件),另一个是HTTP动词 . 必须有两者的定义,您计划使用的方法和动词以及 mapping between them .
它还说,当操作不完全与标准的方法映射(
List
,Get
,Create
,Update
,Delete
在这种情况下),可以使用"Custom methods",像BatchGet
,以检索基于几个对象ID输入多个对象,或SendEmail
.没关系 . 在REST方面,你不能做GET,因为它不可缓存,但是如果你使用POST或PATCH或PUT或其他什么都无关紧要,并且URL无关紧要 . 如果您正在执行REST,重要的是当您从服务器获得资源的表示时,该表示可以为客户端提供状态转换选项 .
如果您的GET响应具有状态转换,则客户端只需要知道如何读取它们,并且服务器可以根据需要更改它们 . 这里使用POST完成更新,但如果更改为PATCH,或者URL更改,则客户端仍然知道如何进行更新:
您可以列出客户端所需的/可选参数,以便回馈给您 . 这取决于应用程序 .
就业务运营而言,这可能是与客户资源相关联的不同资源 . 如果您想向客户发送电子邮件,那么该服务可能是您自己的POST资源,因此您可以在客户资源中包含以下操作:
一些好的视频,以及演示者的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/