RESTful API和批量操作

时间:2017-08-05 19:39:09

标签: rest http api-design bulk optimistic-concurrency

我有一个中间层,它在共享数据库上执行CRUD操作。当我将产品转换为.NET Core时,我认为我也会考虑使用REST作为API,因为CRUD应该是它的功能。似乎REST对于单个记录操作来说是一个很好的解决方案,但是当我想要删除1000条记录时会发生什么?

每个专业的多用户应用程序都会有一些乐观并发检查的概念:你不能让一个用户在没有反馈的情况下消除另一个用户的工作。据我了解,REST使用HTTP ETag头记录处理此问题。如果客户发送的ETag与服务器的标签不匹配,则发出 412 Precondition Failed 。到现在为止还挺好。但是,当我想删除1,000条记录时,我会使用什么? 1,000次单独调用的来回时间是相当可观的,那么REST如何处理涉及乐观并发的批处理操作?

2 个答案:

答案 0 :(得分:4)

REST的重点是资源和客户端与服务器的分离,但它不是简单的CRUD架构或协议。虽然CRUD和REST似乎非常相似,但通过REST原则can often also have sideeffects管理资源。因此,将REST描述为简单的CRUD就是过于简单化了。

关于REST资源的批处理,底层协议(通常是HTTP)确实定义了可以使用的功能。 HTTP定义了几个可用于修改多个资源的操作。

POST是该协议的通用瑞士军刀,可以用来根据你的喜好来管理资源。由于开发人员定义了语义,您可以使用它来一次创建,更新或删除多个资源。

PUT具有使用请求的有效负载主体替换在给定URI处可获得的资源的状态的语义。如果您向"列表" -resource发送PUT请求,并且有效负载定义了条目列表,您也可以实现批处理操作。

  

POST和PUT方法的根本区别在于      通过封闭表示的不同意图突出显示。      POST请求中的目标资源旨在处理      根据资源自身的语义封闭表示,      而PUT请求中的封闭表示定义为      替换目标资源的状态。

...

  

应用于目标资源的PUT请求可能会产生副作用      其他资源。例如,一篇文章可能有一个URI      识别"当前版本" (资源)与...分开      标识每个特定版本的URI(一个点上的不同资源与当前版本共享相同的状态      资源)。成功的PUT请求"当前版本" URI      因此,除了更改之外,还可能会创建新版本资源      目标资源的状态,也可能导致链接      在相关资源之间添加。    (Source

PATCHRFC 5789)尚未包含在HTTP协议中,但有很多框架支持。它主要用于一次更改多个资源或对资源执行部分更新,如果更新的部分是某个其他资源的子资源,PUT也能够实现;在这种情况下,它具有对外部资源进行部分更新的效果。

重要的是要知道PATCH请求包含服务器必须执行的必要步骤才能将资源转换为其预期状态。因此,客户必须抓住当前状态并预先计算转换所需的必要步骤。有关此主题的非常翔实的博客文章是Don't patch like an idiot。这里JSON PatchRFC)是基于JSON的媒体类型,可清楚地显示PATCH概念。必须完全应用补丁请求(补丁请求中定义的每个操作)或根本不应用补丁请求。因此,如果任何操作失败,它需要事务范围处理和回滚。

ETagIfModifiedSince标头等条件请求在RFC 7232中定义,只有在最新版本的资源上应用请求时才能在HTTP请求中使用以执行修改因此与(分布式)数据库中的乐观锁定相关联。

  

到目前为止,这么好。但是,当我想删除1,000条记录时,我会使用什么?

这取决于您将使用的框架。如果它支持PATCH,我会明确投票给PATCH。如果没有,那么使用POSTPUT更加安全,因为非常严格的语义PUT具有,因为语义是由你明确定义的。在批量删除的情况下,也可以通过使用空主体来定位集合资源来使用PUT,其结果是删除集合中的任何项目,从而清除整个集合。如果某些项目仍应保留在集合中,PATCHPOST可能更容易使用。

答案 1 :(得分:0)

如果我理解正确,您需要单独为每条记录提供乐观并发。也就是说,只有当状态符合客户的期望时,才会删除每条记录。 (如果您只想断言整个集合的状态,那么If-Match和412就足够了。)

Roman Vottner的回答在解释所涉及的HTTP方法方面做得非常出色,但我会尝试填写一些细节。

警告经纪人

当我们谈论“REST将如何处理”这个或那个时,您明白技术上您可以使用HTTP作为任何适合您的任何操作的传输。

因此,当您询问REST时,我假设您对uniform interface感兴趣 - 这种方法理论上可以被各种客户端和服务器使用。

但关键词是“理论上”。例如,一旦你定义了自己的媒体类型(你自己的JSON结构),很多一致性就会消耗殆尽,因为无论如何客户端都必须针对你的特定API进行编码,此时你可以要求它跳转通过你想要的任何篮球。

但如果你仍然有兴趣尽可能多地挽救一致性,那就继续阅读。

全部或全无

如果你想要一个全有或全无的操作,如果任何一个单独的前提条件失败,它将完全失败,那么,正如Roman建议的那样,你可以使用PATCH格式的JSON Patch。为此,您需要将集合的概念表示形式作为要应用修补程序的单个JSON对象。

例如,假设您拥有/my/collection/1/my/collection/4等资源。您可以将/my/collection/表示为:

{
    "resources": {
        "1": {
            "href": "1",
            "etag": "\"BRkDVtYw\"",
            "name": "Foo Bar",
            "price": 1234.5,
            ...
        },
        "4": {
            "href": "4",
            "etag": "\"RCi8knuN\"",
            "name": "Baz Qux",
            "price": 2345.6,
            ...
        },
        ...
    }
}

此处,"1""4"是相对于/my/collection/的网址。您可以使用特定于域的ID,但正确的REST根据不透明的URL进行操作。

标准不要求您在GET /my/collection/上实际提供此表示,但如果您确实支持此类请求,则应使用该表示。无论如何,对于此结构,您可以应用以下JSON补丁:

PATCH /my/collection/ HTTP/1.1
Content-Type: application/json-patch+json

[
    {"op": "test", "path": "/resources/1/etag", "value": "\"BRkDVtYw\""},
    {"op": "remove", "path": "/resources/1"},
    {"op": "test", "path": "/resources/4/etag", "value": "\"RCi8knuN\""},
    {"op": "remove", "path": "/resources/4"},
    ...
]

此处,path不是网址路径,而是上述表示中的JSON pointer

如果所有修补程序操作都成功,那么您会使用204 (No Content)200 (OK)等成功的状态代码进行回复。

如果任何ETag test操作失败,请回复409 (Conflict)。在这种情况下,您不应回复412 (Precondition Failed),因为请求本身没有前提条件(如If-Match)。

如果出现其他任何问题,请回复其他相应的状态代码:请参阅RFC 5789 § 2.2RFC 7231 § 6.6

混合结果

如果你不想要“全有或全无”的语义,那么我不知道任何标准化的解决方案。正如Roman所说,在这种情况下你不能使用PATCH方法,但你可以使用自定义媒体类型的POST(RFC 6838 § 3.4)。它看起来像这样:

POST /my/collection/ HTTP/1.1
Content-Type: application/x.my-patch+json
Accept: application/x.my-patch-results+json

{
    "delete": [
        {"href": "1", "if-match": "\"BRkDVtYw\""},
        {"href": "4", "if-match": "\"RCi8knuN\""},
        ...
    ]
}

您可以使用200(OK)响应此类请求,无论是否有任何单个删除操作成功。另一种选择是207 (Multi-Status),但在这种情况下我没有看到任何好处,并且它没有在WebDAV之外广泛使用,因此Postel’s law建议不要去那里。

HTTP/1.1 200 OK
Content-Type: application/x.my-patch-results+json

{
    "delete": [
        {"href": "1", "success": true},
        {"href": "4", "success": false, "error": {...}},
        ...
    ]
}

当然,如果补丁首先无效,则您应该根据需要使用415 (Unsupported Media Type)422 (Unprocessable Entity)进行回复。

另一个角度

  

1000次个人通话的来回时间相当可观

它在HTTP / 1.1中。但是,如果您可以使用HTTP / 2 - 它对并发请求提供了更好的支持,以及每个请求的更小的网络开销 - 那么1000个单独的请求可能会对您有用。