我有一个中间层,它在共享数据库上执行CRUD操作。当我将产品转换为.NET Core时,我认为我也会考虑使用REST作为API,因为CRUD应该是它的功能。似乎REST对于单个记录操作来说是一个很好的解决方案,但是当我想要删除1000条记录时会发生什么?
每个专业的多用户应用程序都会有一些乐观并发检查的概念:你不能让一个用户在没有反馈的情况下消除另一个用户的工作。据我了解,REST使用HTTP ETag头记录处理此问题。如果客户发送的ETag与服务器的标签不匹配,则发出 412 Precondition Failed 。到现在为止还挺好。但是,当我想删除1,000条记录时,我会使用什么? 1,000次单独调用的来回时间是相当可观的,那么REST如何处理涉及乐观并发的批处理操作?
答案 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)
PATCH
(RFC 5789)尚未包含在HTTP协议中,但有很多框架支持。它主要用于一次更改多个资源或对资源执行部分更新,如果更新的部分是某个其他资源的子资源,PUT
也能够实现;在这种情况下,它具有对外部资源进行部分更新的效果。
重要的是要知道PATCH
请求包含服务器必须执行的必要步骤才能将资源转换为其预期状态。因此,客户必须抓住当前状态并预先计算转换所需的必要步骤。有关此主题的非常翔实的博客文章是Don't patch like an idiot。这里JSON Patch(RFC)是基于JSON的媒体类型,可清楚地显示PATCH概念。必须完全应用补丁请求(补丁请求中定义的每个操作)或根本不应用补丁请求。因此,如果任何操作失败,它需要事务范围处理和回滚。
ETag
和IfModifiedSince
标头等条件请求在RFC 7232中定义,只有在最新版本的资源上应用请求时才能在HTTP请求中使用以执行修改因此与(分布式)数据库中的乐观锁定相关联。
到目前为止,这么好。但是,当我想删除1,000条记录时,我会使用什么?
这取决于您将使用的框架。如果它支持PATCH
,我会明确投票给PATCH
。如果没有,那么使用POST
比PUT
更加安全,因为非常严格的语义PUT
具有,因为语义是由你明确定义的。在批量删除的情况下,也可以通过使用空主体来定位集合资源来使用PUT
,其结果是删除集合中的任何项目,从而清除整个集合。如果某些项目仍应保留在集合中,PATCH
或POST
可能更容易使用。
答案 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.2和RFC 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个单独的请求可能会对您有用。