简体   繁体   English

RESTful API和批量操作

[英]RESTful API and bulk operations

I have a middle tier which performs CRUD operations on a shared database. 我有一个中间层,它在共享数据库上执行CRUD操作。 When I converted the product to .NET Core I thought I'd also look at using REST for the API as CRUD is supposed to be what it does well. 当我将产品转换为.NET Core时,我认为我也会考虑使用REST作为API,因为CRUD应该是它的功能。 It seems like REST is a great solution for single record operations, but what happens when I want to delete, say, 1,000 records? 似乎REST对于单个记录操作来说是一个很好的解决方案,但是当我想要删除1000条记录时会发生什么?

Every professional multi-user application is going to have some concept of Optimistic Concurrency checking: you can't have one user wipe out the work of another user without some feedback. 每个专业的多用户应用程序都会有一些乐观并发检查的概念:如果没有一些反馈,你就不能让一个用户消除另一个用户的工作。 As I understand it, REST handles this with the HTTP ETag header record. 据我了解,REST使用HTTP ETag头记录处理此问题。 If the ETag send by the client doesn't match the server's tag, then you issue a 412 Precondition Failed . 如果客户端发送的ETag与服务器的标记不匹配,则发出412 Precondition Failed So far, so good. 到现在为止还挺好。 But what do I use when I want to delete 1,000 records? 但是,当我想删除1,000条记录时,我会使用什么? The back-and-forth time for 1,000 individual calls is considerable, so how would REST handle a batch operation that involved Optimistic Concurrency ? 1,000次单独调用的来回时间是相当可观的,那么REST如何处理涉及Optimistic Concurrency的批处理操作?

RESTs focus is on resources and the decoupling of clients from servers, it is though not a simple CRUD architecture or protocol. REST的重点是资源和客户端与服务器的分离,但它不是简单的CRUD架构或协议。 While CRUD and REST seem to be very similar, managing resources through REST principles can often also have sideeffects . 虽然CRUD和REST看起来非常相似,但通过REST原则管理资源通常也会产生副作用 Therefore, describing REST as simple CRUD thing is an oversimplification. 因此,将REST描述为简单的CRUD是过于简单化。

In regards to batch-processing of REST resources, the underlying protocol (most often HTTP) does define the capabilities that can be used. 关于REST资源的批处理,底层协议(通常是HTTP)确实定义了可以使用的功能。 HTTP defines a couple of operations that can be used to modify multiple resources. HTTP定义了一些可用于修改多个资源的操作。

POST is the all-purpose, swiss-army knife of the protocol and can be used to literally manage resources to your likings. POST是该协议的通用瑞士军刀,可以用来根据您的喜好来管理资源。 As the semantics are defined by the developer you can use it to create, update or delete multiple resources at once. 由于开发人员定义了语义,您可以使用它来一次创建,更新或删除多个资源。

PUT has the semantics of replacing the state of a resource obtainable at a given URI with the payload body of the request. PUT具有用请求的有效负载主体替换在给定URI处可获得的资源的状态的语义。 If you send a PUT request to a "list"-resource and the payload defines a list of entries, you can achieve a batch operation as well. 如果将PUT请求发送到“list”-resource并且有效负载定义了条目列表,则也可以实现批处理操作。

The fundamental difference between the POST and PUT methods is highlighted by the different intent for the enclosed representation. POST和PUT方法之间的根本区别在于封闭表示的不同意图。 The target resource in a POST request is intended to handle the enclosed representation according to the resource's own semantics, whereas the enclosed representation in a PUT request is defined as replacing the state of the target resource. POST请求中的目标资源旨在根据资源自身的语义处理封闭的表示,而PUT请求中的封闭表示被定义为替换目标资源的状态。

... ...

A PUT request applied to the target resource can have side effects on other resources. 应用于目标资源的PUT请求可能对其他资源产生副作用。 For example, an article might have a URI for identifying "the current version" (a resource) that is separate from the URIs identifying each particular version (different resources that at one point shared the same state as the current version resource). 例如,文章可能具有用于标识“当前版本”(资源)的URI,该URI与标识每个特定版本的URI(在一个点上共享与当前版本资源相同的状态的不同资源)分开。 A successful PUT request on "the current version" URI might therefore create a new version resource in addition to changing the state of the target resource, and might also cause links to be added between the related resources. 因此,对“当前版本”URI的成功PUT请求可能除了改变目标资源的状态之外还创建新版本资源,并且还可能导致在相关资源之间添加链接。 ( Source ) 来源

PATCH ( RFC 5789 ) is not yet included in the HTTP protocol, though supported by plenty frameworks. PATCHRFC 5789 )尚未包含在HTTP协议中,尽管有很多框架支持。 It is primarily used to alter multiple resources at once or to perform partial updates on resources, which PUT is also able to achieve if the updated part is a sub-resource of some other resource; 它主要用于一次更改多个资源或对资源执行部分更新,如果更新的部分是某个其他资源的子资源, PUT也能够实现; in that case it has the effect of a partial update on the outer resource. 在这种情况下,它具有对外部资源进行部分更新的效果。

It is important to know that a PATCH request contains the necessary steps a server has to fulfill to transform a resource to its intended state. 重要的是要知道PATCH请求包含服务器必须执行的必要步骤以将资源转换为其预期状态。 A client therefore has to grab the current state and calculate the necessary steps needed for the transformation beforehand. 因此,客户必须抓住当前状态并预先计算转换所需的必要步骤。 A very informative blog post on this topic is Don't patch like an idiot . 关于这个主题的一篇非常翔实的博客文章是“ 不要像傻瓜一样修补” Here JSON Patch ( RFC ) is a JSON based media type that visualizes the PATCH concept clearly. 这里JSON PatchRFC )是一种基于JSON的媒体类型,可以清晰地显示PATCH概念。 A patch request has to be applied either fully (each operation defined in the patch request) or applied not at all. 必须完全应用补丁请求(补丁请求中定义的每个操作)或根本不应用补丁请求。 It therefore requires a transaction scoped handling and a roll back in case any of the operations failed. 因此,在任何操作失败的情况下,它需要事务范围处理和回滚。

Conditional requests like ETag and IfModifiedSince headers are defined in RFC 7232 and can be used in HTTP requests to perform the modifications only if the request is applied on the most recent version of resource and therefore correlates to an optimistic locking in (distributed) databases. ETagIfModifiedSinceIfModifiedSince条件请求在RFC 7232中定义,并且只有在请求应用于最新版本的资源时才能在HTTP请求中用于执行修改,因此与(分布式)数据库中的乐观锁定相关。

So far, so good. 到现在为止还挺好。 But what do I use when I want to delete 1,000 records? 但是,当我想删除1,000条记录时,我会使用什么?

This depends on what framework you'll use. 这取决于您将使用的框架。 If it supports PATCH I clearly vote for PATCH . 如果它支持PATCH我清楚地投票给PATCH In case it does not, you are probably safer to use POST than PUT as of the very restrictive semantics PUT has, as the semantics are clearly defined by you then. 如果它没有,你可能比PUT更安全使用POST ,因为PUT具有非常严格的语义,因为语义是由你明确定义的。 In case of a batch-delete, PUT can also be used by targeting the collection resource with an empty body which has the result of removing any items in the collection and therefore clearing the whole collection. 在批量删除的情况下,也可以通过使用空体来定位集合资源来使用PUT ,该空体具有移除集合中的任何项目并因此清除整个集合的结果。 If some of the items should remain in the collection though, PATCH or POST are probably more easy to use. 如果某些项目仍应保留在集合中,则PATCHPOST可能更容易使用。

If I understand correctly, you want optimistic concurrency for each record individually. 如果我理解正确,您需要单独为每条记录提供乐观并发。 That is, each record is to be deleted only if its state matches the client's expectation. 也就是说,只有当状态符合客户的期望时,才会删除每条记录。 (If you only want to assert the entire collection's state, then If-Match and 412 are sufficient.) (如果你只想断言整个集合的状态,那么If-Match和412就足够了。)

Roman Vottner's answer does an excellent job of explaining the HTTP methods involved, but I'll try to fill in some details. Roman Vottner的回答在解释所涉及的HTTP方法方面做得非常出色,但我会尝试填写一些细节。

Caveat emptor 买者自负

When we talk about “how would REST handle” this or that, you understand that technically you can use HTTP as a transport for any operation in any way that suits you. 当我们谈论“REST将如何处理”这个或那个时,您就会明白,从技术上讲,您可以以任何适合您的方式使用HTTP作为传输。

So when you're asking about REST, I'm assuming you're interested in a uniform interface — an approach that could theoretically be used by a range of various clients and servers. 因此,当您询问REST时,我假设您对统一接口感兴趣 - 这种方法理论上可以被各种客户端和服务器使用。

But the key word there is “theoretically”. 但关键词是“理论上”。 For example, once you define your own media type (your own JSON structure), a lot of the uniformity goes down the drain, because a client would have to be coded against your specific API anyway, at which point you can ask it to jump through any hoops you want. 例如,一旦你定义了自己的媒体类型(你自己的JSON结构),很多一致性就会消耗殆尽,因为无论如何客户端都必须针对你的特定API进行编码,此时你可以要求它跳转通过你想要的任何篮球。

But if you're still interested in salvaging as much of the uniformity as possible, then read on. 但如果你仍然有兴趣尽可能多地挽救一致性,那么请继续阅读。

All or nothing 全有或全无

If you want an all-or-nothing operation, which fails entirely if any of the individual preconditions fails, then, as Roman suggests, you can use PATCH with the JSON Patch format. 如果你想要一个全有或全无的操作,如果任何一个单独的前置条件失败,它将完全失败,那么,正如Roman建议的那样,你可以使用PATCHJSON Patch格式。 For this, you need a conceptual representation of your collection as a single JSON object, to which the patch is to be applied. 为此,您需要将集合的概念表示形式作为要应用修补程序的单个JSON对象。

For example, suppose you have resources like /my/collection/1 , /my/collection/4 , and so on. 例如,假设您有/my/collection/1/my/collection/4等资源。 You could represent /my/collection/ as: 您可以将/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,
            ...
        },
        ...
    }
}

Here, "1" and "4" are URLs relative to /my/collection/ . 这里, "1""4"是相对于/my/collection/ URL。 You could use domain-specific IDs instead, but proper REST operates in terms of opaque URLs. 您可以使用特定于域的ID,但正确的REST根据不透明URL进行操作。

The standards don't require you to actually serve this representation on GET /my/collection/ , but if you do support such a request, then you should use that representation. 标准不要求您在GET /my/collection/上实际提供此表示,但如果您确实支持此类请求,则应使用该表示。 Anyway, to this structure you can apply the following JSON patch: 无论如何,对于此结构,您可以应用以下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"},
    ...
]

Here, path is not a URL path, it's a JSON pointer into the above representation. 这里, path不是URL路径,它是进入上述表示的JSON指针

If all patch operations succeed, then you respond with a successful status code like 204 (No Content) or 200 (OK) . 如果所有修补程序操作都成功,那么您将使用成功的状态代码(如204(无内容)200(确定))进行响应。

If any of the ETag test operations fails, you respond with 409 (Conflict) . 如果任何ETag test操作失败,则以409(冲突)响应。 You should not respond with 412 (Precondition Failed) in this case, because there is no precondition (like If-Match ) on the request itself. 在这种情况下,您不应该回复412(Precondition Failed) ,因为请求本身没有前提条件(如If-Match )。

If anything else goes wrong, you respond with other appropriate status codes: see RFC 5789 § 2.2 and RFC 7231 § 6.6 . 如果出现其他任何问题,您可以使用其他适当的状态代码进行响应:请参阅RFC5789§2.2RFC7231§6.6

Mixed result 混合的结果

If you don't want “all-or-nothing” semantics, then I'm not aware of any standardized solution. 如果你不想要“全有或全无”的语义,那么我不知道任何标准化的解决方案。 As Roman notes, you cannot use the PATCH method in this case, but you can use POST with a custom media type ( RFC 6838 § 3.4 ). 正如Roman所说,在这种情况下你不能使用PATCH方法,但你可以使用自定义媒体类型的POST( RFC6838§3.4 )。 It could look like this: 它可能看起来像这样:

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\""},
        ...
    ]
}

You can respond to such a request with 200 (OK), regardless of whether any of the individual deletes succeeded. 无论是否有任何删除成功,您都可以使用200(OK)响应此类请求。 Another option would be 207 (Multi-Status) , but I don't see any benefits to it in this case, and it's not widely used outside of WebDAV, so Postel's law would suggest not going there. 另一种选择是207(多状态) ,但在这种情况下我没有看到任何好处,并且它没有在WebDAV之外广泛使用,因此Postel的定律建议不要去那里。

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

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

Of course, if the patch was invalid in the first place, you should instead respond with 415 (Unsupported Media Type) or 422 (Unprocessable Entity) as appropriate. 当然,如果补丁首先无效,则应该适当地使用415(不支持的媒体类型)422(不可处理的实体)进行响应。

Another angle 另一个角度

The back-and-forth time for 1,000 individual calls is considerable 1000个人通话的来回时间相当可观

It is in HTTP/1.1. 它在HTTP / 1.1中。 But if you can use HTTP/2 — which has much better support for concurrent requests, as well as much smaller network overhead per request — then 1000 individual requests might work out just fine for you. 但是,如果您可以使用HTTP / 2(它对并发请求有更好的支持,以及每个请求的小得多的网络开销),那么1000个单独的请求可能会很好地为您服务。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM