乐观并发的版本号与 ETag

version number vs ETag for optimistic concurrency

我正在用 JSON 响应实现我的第一个 Web API,并且对乐观锁定的并发控制处理感到困惑。现在我用 ASP.NET Core 6 开发服务器,用 Angular 开发客户端。数据存储是一个 SQL 数据库。将来我们想向第三方开放API。

我看到两个处理乐观锁定的选项:

A) header

中的 ETag

B)版本号在body

对于选项 A) 我看到了这两个问题:

  1. 由于 ETag 是 header 失败时需要发送“先决条件失败 412”。错误需要在body中指定,这样客户端才能理解为并发错误。

对于并发错误,我希望发送 HTTP 错误代码 409:

"...冲突最有可能发生在对 PUT 请求的响应中。例如,如果正在使用版本控制并且被 PUT 的实体包含对资源的更改,而这些更改与所做的更改发生冲突对于较早的 (third-party) 请求,服务器可能会使用 409 响应来指示它无法完成请求。在这种情况下,响应实体可能会包含两个版本之间的差异列表由响应定义的格式 Content-Type." 2) 当从服务器返回 collection/list 表示(例如所有到期订单)时,不可能发送多个 ETag。 header中的ETag适用于整个collection/list,但列表中的每个单个资源都可以被不同的用户单独修改,需要有一个版本号用于并发检测。除了将 number/ETag 作为 属性 与 body.

中的每个表示一起发送之外,我没有看到任何其他选择

当 collection 与单一资源的处理方式不同时,我感到很困惑。由于客户端需要为每个资源存储 ETag,因此很自然地将 ETag 作为 属性 包含在其 object 模型中。

选项 B) 避免了 A 的问题): 我可以在失败时发送 409,单个资源和 collections 资源以相同的方式处理。问题是 DELETE 动词。

关于在 [1] 的 DELETE 请求中使用 body 的讨论:

当前版本的 RFC 声明:

"客户端不应该在 DELETE 请求中生成 body。在 DELETE 请求中收到的有效负载没有定义的语义,不能改变请求的含义或目标,并且可能会导致某些实现拒绝请求。"

“没有定义的语义”是什么意思?有什么影响?我知道有些实现忽略了 body 或者没有实现发送带有 DELETE 的 body 的选项。我仍然想知道我是否可以在 body 中发送一个版本号。通常,我不明白不应该为 DELETE 生成 body 的原因?为什么它是邪恶的?

我知道带 ETag 的 If-Match 是处理并发的推荐解决方案,但它不能处理基本的 collection。当推荐的解决方案 ETag 不能使用它来兼容时,为什么会出现并发问题的 409 错误代码?这真是令人费解。

将版本号保留在 body 中用于并发检测将是一致的,但它不适用于 DELETE。

已编辑:

以上文字经过编辑,以反映 Everts 的评论。

我看到以下选项(即使GET也可以是有条件的我在这里只关心并发更新问题):

A) 客户端请求使用 If-Match header。服务器响应将 ETag 放入 header 和 body

body 中的 ETag 为客户端提供了处理单个资源和 collection 的一致方式。如果 collection 需要自己的版本,header 中的 ETag 可用,也可用于条件 GET。

并发错误我不得不放弃发送409

B) 版本号在 body 中发送,DELETE 除外。

在 DELETE 的情况下,版本号必须与查询参数或 If-Match header 一起发送。有这个异常是不愉快的,但是可以发送更清晰的 409 并且单个资源和 collections 得到一致的处理。

我仍然不确定要选择哪种实施方式。是否有任何明确的标准可以帮助做出决定?其他人是如何解决这个问题的?

我是不是误解了409的用法?

更新:

这篇德语文章 https://www.seoagentur.de/seo-handbuch/lexikon/s/statuscode-412-precondition-failed/ 解释了 409 与 412 的用法:

412 仅应在先决条件(例如 If-Match)导致失败时使用,而 409 应在实体会导致冲突时使用。如果使用带有版本号的数据库s id 可以传递给 ETag 并从 If-Match 传回版本 id。但是,在实体本身的body中并不禁止这样做。它只需要解释概念及其工作原理,而 ETag 解决方案仅让您参考 HTTP 规范。 (评论:还是留下了no body in DELETE的问题)

带有 collections 问题的 ETag 有更多细微差别:

a) 如果返回的 collection 只是列表视图的数据,它通常不会包含显示的 object 的所有数据。如果列表中的一项被编辑,客户端需要首先从单个资源 URI 中获取完整的实体,然后通过该请求获取所需的 ETag。它还提供了编辑时而非请求列表时资源的当前版本。

b) 如果返回的 collection 包含完整的实体数据并且性能问题是相关的,或者许多项目需要立即单独更改,那么每个项目的 ETag 可以传递给客户端body.

更新二:

“Zalando RESTfull API 和事件指南”在 [https://opensource.zalando.com/restful-api-guidelines/#optimistic-locking][ 比较了优化锁定的各种选项2]

他们赞成在 body 中发送 ETag 用于服务器响应。

我目前认为这是最好的选择。客户端可以自由使用该 ETag 或在单个资源上发送新的 Get。

To me "Conflict 409" is the right response for a concurrency error, but the RFC states that a "Precondition Fails 412" needs to be send, which is not clear enough. Of course an error description in the body can clarify the cause of the error, but I would rather send 409.

使用定义的 HTTP 状态代码,而不是您认为它们根据名称应该表示的意思。

When a collection (e.g. all due orders) is returned from the server there is no possibility to send multiple ETags. Each single resource can be individually modified. Therefore the only option is to send the ETag in the body as a property of each representation in the JSON response. It is confusing that collections are treated differently than single resources. Also the client needs to include a property for the ETag in its object model in any case.

HTTP 并没有真正的 collection 概念,但您也可以为 collection 本身提供自己的 ETag。

I don't understand all the implications of the specific wording. What does "has no defined semantics" mean? What is the reasons a body should not be generated?

删除请求应该只删除位于 URI 的资源。如果成功,我们可以假设 DELETE 请求中使用的 URI 在请求成功后将 return 404 或 410。

如果你想有条件地参数化删除,一次删除多个资源或者删除 URI 中指定的资源以外的东西,DELETE 请求根本不适合 use-case。

如果您想使用 ETag,只需使用 If-Match header。