在RESTful服务中进行部分更新的最佳做法

我正在为客户pipe理系统编写一个RESTful服务,我试图find部分更新logging的最佳实践。 例如,我希望调用者能够通过GET请求读取完整的logging。 但是为了更新,只允许logging中的某些操作,比如将状态从ENABLED改为DISABLED。 (我有比这更复杂的情况)

我不希望调用者提交整个logging只是出于安全原因更新的字段(这也感觉像矫枉过正)。

有build议的URI的build议方式? 在阅读REST书籍时,RPC风格的调用似乎被忽略了。

如果以下呼叫以id 123返回客户的完整客户logging

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 

谢谢弗兰克

你基本上有两个select:

  1. 使用PATCH (但请注意,您必须定义自己的媒体types,指定准确发生的事情)

  2. 使用POST到一个子资源并返回303请参阅其他与指向主资源的位置标题。 303的意图是告诉客户:“我已经执行了你的POST,效果是其他资源已经被更新了,查看哪个资源的位置标题。 POST / 303旨在迭代添加资源来build立一些主要资源的状态,并且它是部分更新的完美select。

您应该使用POST进行部分更新。

要更新客户123的字段,请向/ customer / 123发送POST。

如果您只想更新状态,您也可以将其设置为/ customer / 123 / status。

一般来说,GET请求不应该有任何副作用,PUT是写/replace整个资源。

这直接来自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 /补丁 )或XML补丁框架(参见http://tools.ietf.org/html/rfc5261 )。 在我看来,json-patch是最适合您的业务数据的types。

使用JSON / XML补丁文件的PATCH对于部分更新具有非常前沿的语义。 如果您开始使用POST,并使用原始文档的修改副本,对于部分更新,您很快就会遇到希望缺less值(或者更确切地说是空值)的问题,以表示“忽略此属性”或“将此属性设置为空值“ – 并导致黑客攻击解决scheme的漏洞,最终将导致你自己的补丁格式。

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

我遇到了类似的问题。 当您只想更新一个字段时,在子资源上放置PUT似乎可行。 但是,有时候你想要更新一些东西:想想一个代表资源的Web表单,可以select更改一些条目。 用户提交的表单不应该导致多个PUT。

以下是我能想到的两个解决scheme:

  1. 用整个资源做一个PUT。 在服务器端,定义PUT与整个资源忽略所有没有改变的值的语义。

  2. 用部分资源做一个PUT。 在服务器端,定义这个合并的语义。

2只是1的带宽优化。有时1是唯一的select,如果资源定义一些字段是必需的字段(认为原始缓冲区)。

这两种方法的问题是如何清除一个领域。 您将必须定义一个特殊的空值(特别是对于原始缓冲区,因为原始缓冲区没有定义空值),这将导致清除字段。

注释?

东西要添加到你的扩充问题。 我认为你可以完美地devise更复杂的商业行为。 但是你必须放弃资源和动词的思维方式/程序风格,多思考。

邮件发送

POST /customers/123/mails payload: {from: x@x.com, subject: "foo", to: y@y.com}
POST /customers/123/mails payload: {from: x@x.com, subject: "foo", to: y@y.com} 

这个资源+ POST的实现将发送邮件。 如有必要,您可以提供/ customer / 123 / outbox之类的东西,然后提供资源链接到/ customer / mail / {mailId}。

顾客数量

您可以像处理search资源一样处理它(包括带有分页和数字查找信息的search元数据,从而为您提供客户数量)。

GET /customers response payload: {numFound: 1234, paging: {self:..., next:..., previous:...} customer: { ...} ....}
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属性被更新,其他值被保存。

使用PUT更新不完整/部分资源。

您可以接受jObject作为参数并parsing其值以更新资源。

以下是您可以用作参考的function:

 public IHttpActionResult Put(int id, JObject partialObject) { Dictionary<string, string> dictionaryObject = new Dictionary<string, string>(); foreach (JProperty property in json.Properties()) { dictionaryObject.Add(property.Name.ToString(), property.Value.ToString()); } int id = Convert.ToInt32(dictionaryObject["id"]); DateTime startTime = Convert.ToDateTime(orderInsert["AppointmentDateTime"]); Boolean isGroup = Convert.ToBoolean(dictionaryObject["IsGroup"]); //Call function to update resource update(id, startTime, isGroup); return Ok(appointmentModelList); } 

为了修改状态,我认为RESTful方法是使用描述资源状态的逻辑子资源。 这个海事组织是非常有用和干净的,当你有一个减less的状态集。 它使您的API更具performance力,而不会强迫您的客户资源的现有操作。

例:

 POST /customer/active <-- Providing entity in the body a new customer { ... // attributes here except status } 

POST服务应该返回新创build的客户ID:

 { id:123, ... // the other fields here } 

创build资源的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 } 

您正在为您的客户资源创build概念性子资源。 这与Roy Fielding对资源的定义也是一致的:“资源是对一组实体的概念映射,而不是对应于任何特定时间点映射的实体……”在这种情况下,概念图是活动的 – 客户与客户的状态= ACTIVE。

阅读操作:

 GET /customer/123/active GET /customer/123/inactive 

如果你在这些调用之一后立即返回状态404,则成功的输出可能不包括隐含的状态。 当然,您仍然可以使用GET / customer / 123?status = ACTIVE | INACTIVE直接查询客户资源。

DELETE操作很有趣,因为语义可能会令人困惑。 但是,您可以select不发布该概念资源的操作,也可以根据业务逻辑使用它。

 DELETE /customer/123/active 

这可以使您的客户进入DELETED / DISABLED状态或到相反的状态(ACTIVE / INACTIVE)。

关于你的更新。

我相信CRUD的概念在APIdevise上引起了一些混淆。 CRUD是基本操作对数据执行的一般低级概念,HTTP动词只是21年前创build的请求方法,可能映射到或不映射到CRUD操作。 实际上,尝试在HTTP 1.0 / 1.1规范中findCRUD首字母缩写词的存在。

在Google云平台API文档中可以find适用务实惯例的很好的解释指南。 它描述了创build基于资源的API背后的概念,其中强调了大量的操作资源,并且包含了您所描述的用例。 虽然这只是他们产品的常规devise,但我觉得这很有道理。

这里的基本概念(和产生很多混淆的概念)是“方法”和HTTP动词之间的映射。 一件事是定义你的API将在哪些types的资源(例如,获取客户列表或发送电子邮件)上执行什么“操作”(方法),另一个是HTTP动词。 必须有一个你打算使用的方法和动词的定义,以及它们之间映射

它还说,当一个操作没有与一个标准的方法(在这种情况下是ListGetCreateUpdateDelete )完全映射时,可以使用“自定义方法”,如BatchGet ,它根据多个对象检索多个对象IDinput或SendEmail

没关系。 就REST而言,你不能做一个GET,因为它不是可caching的,但是使用POST或者PATCH或者PUT或者其他什么都没有关系,并且这个URL看起来并不重要。 如果你正在做REST,重要的是,当你从服务器获得你的资源的表示时,这个表示可以给客户端状态转换选项。

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

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

您可以尽可能列出客户的必需/可选参数,以回馈给您。 这取决于应用程序。

就业务运营而言,这可能是与客户资源相关联的不同资源。 如果您想发送电子邮件给客户,也许该服务是您可以发送到的自己的资源,因此您可能在客户资源中包含以下操作:

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

一些好的video,演示者的REST架构的例子是这些。 Stormpath只使用GET / POST / DELETE,这很好,因为REST与您使用的操作或URL的外观无关(除GETs应该可caching外):

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