我正在为客户管理系统编写一个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 

当前回答

我也遇到了类似的问题。当您只想更新单个字段时,子资源上的PUT似乎可以工作。然而,有时你想要更新一堆东西:考虑一个web表单表示资源,选项更改一些条目。用户提交的表单不应该导致多个put。

以下是我能想到的两种解决方案:

对整个资源执行PUT。在服务器端,定义具有整个资源的PUT忽略所有未更改的值的语义。 使用部分资源执行PUT。在服务器端,将此语义定义为合并。

2就是1的带宽优化。有时1是唯一的选项,如果资源定义了一些字段是必需的字段(想想proto缓冲区)。

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

评论?

其他回答

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

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

下面是你可以参考的Java函数:

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);
}

你基本上有两个选择:

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.

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

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

MERGE /customer/123

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

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

您应该使用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。

RFC 7396: JSON Merge Patch(在这个问题发布四年后发布)从格式和处理规则方面描述了Patch的最佳实践。

简而言之,您向目标资源提交一个HTTP PATCH,它带有application/merge-patch+json MIME媒体类型和一个只表示您想要更改/添加/删除的部分的主体,然后遵循以下处理规则。

规则:

如果提供的合并补丁包含未出现在目标中的成员,则会添加这些成员。 如果目标不包含该成员,则替换该值。 合并补丁中的空值被赋予特殊含义,以指示删除目标中的现有值。

演示上述规则的示例测试用例(见RFC的附录):

 ORIGINAL         PATCH           RESULT
--------------------------------------------
{"a":"b"}       {"a":"c"}       {"a":"c"}

{"a":"b"}       {"b":"c"}       {"a":"b",
                                 "b":"c"}
{"a":"b"}       {"a":null}      {}

{"a":"b",       {"a":null}      {"b":"c"}
"b":"c"}

{"a":["b"]}     {"a":"c"}       {"a":"c"}

{"a":"c"}       {"a":["b"]}     {"a":["b"]}

{"a": {         {"a": {         {"a": {
  "b": "c"}       "b": "d",       "b": "d"
}                 "c": null}      }
                }               }

{"a": [         {"a": [1]}      {"a": [1]}
  {"b":"c"}
 ]
}

["a","b"]       ["c","d"]       ["c","d"]

{"a":"b"}       ["c"]           ["c"]

{"a":"foo"}     null            null

{"a":"foo"}     "bar"           "bar"

{"e":null}      {"a":1}         {"e":null,
                                 "a":1}

[1,2]           {"a":"b",       {"a":"b"}
                 "c":null}

{}              {"a":            {"a":
                 {"bb":           {"bb":
                  {"ccc":          {}}}
                   null}}}