1

我正在设计一个 Web 服务来定期接收列表更新。此时,列表仍然可以建模为单个实体 ( /lists/myList) 或具有许多资源的实际集合 ( /lists/myList/entries/<ID>)。列表很大(数百万个条目),更新很小(通常少于 10 次更改)。

客户端将获取要分发的 Web 服务 URL 和列表,例如:

然后它将按配置推送列表和更新。Web 服务 URL 后面是否有一些数据库很可能但尚未确定。

我一直在研究,似乎使用 JSON 补丁格式的 HTTP PATCH 是最好的方法。

背景和示例:每个列表都有一个识别名称、一个优先级和数百万个条目。每个条目都有一个 ID(由客户端确定)和几个可选属性。创建具有优先级 1 和两个列表条目的列表“requiredItems”的示例:

PUT /lists/requiredItems
Content-Type: application/json


{
  "priority": 1,
  "entries": {
    "1": {
      "color": "red",
      "validUntil": "2016-06-29T08:45:00Z"
    },
    "2": {
      "country": "US"
    }
  }
}

对于更新,客户端首先需要知道列表现在在服务器上的样子。为此,我将向列表实体添加一个属性“修订”。

然后,我会查询这个属性:

GET /lists/requiredItems?property=revision

然后客户端会看到服务器上的版本和客户端已知的最新版本之间需要更改的内容,并编写一个 JSON 补丁。例子:

PATCH /list/requiredItems
Content-Type: application/json-patch+json

[
  { "op": "test", "path": "revision", "value": 3 },
  { "op": "add", "path": "entries/3", "value": { "color": "blue" } },
  { "op": "remove", "path": "entries/1" },
  { "op": "remove", "path": "entries/2/country" },
  { "op": "add", "path": "entries/2/color", "value": "green" },
  { "op": "replace", "path": "revision", "value": 10 }
]

问题:

  • 由于不经常使用的 HTTP 动词 PATCH,这种方法的缺点是客户端支持略少。在不牺牲 HTTP 兼容性(幂等性等)的情况下,是否有更兼容的方法?
  • 将单个列表条目建模为单独的资源并使用PUTand DELETE(可能与ETagand/or If-Match)似乎是一种选择(PUT /lists/requiredItems/entries/3, DELETE /lists/requiredItems/entries/1 PUT /lists/requiredItems/revision),但是当网络落在更新链的中间时,我如何确保应用所有这些操作?HTTP PATCH 是否允许在多个资源上工作?
  • 有没有更好的方法来“版本”列表,也许隐含地也改进了它们的更新方式?请注意,客户端确定修订号。
  • 用 查询修订号是否正确GET /lists/requiredItems?property=revision?它应该是一个单独的资源/lists/requiredItems/revision吗?如果它应该是一个单独的资源,我将如何自动更新它(即列表和修订都更新或都没有更新)?
  • 它可以在 JSON 补丁中首先测试修订值3,然后将其更新到10同一个补丁中吗?
4

2 回答 2

2

由于不经常使用的 HTTP 动词 PATCH,这种方法的缺点是客户端支持略少。

据我所知,PATCH 仅适用于您的服务器就像一个愚蠢的文档存储,其中的操作实际上是“请根据以下描述更新您的文档副本”。

因此,如果您的资源真的只是一个 JSON 文档,它描述了一个包含数百万条目的列表,那么 JSON-Patch 是一个很好的答案。

但是,如果您期望补丁会作为副作用更新您域中的实体,那么我很怀疑。

HTTP PATCH 是否允许在多个资源上工作?

RFC 5789

PATCH 方法会影响 Request-URI 标识的资源,它也可能对其他资源产生副作用

我不热衷于查询修订号;与使用 ETag/If-Match 方法相比,它似乎没有任何明显的优势。一些明显的缺点——你和客户端之间的缓存不知道列表和版本号是相关的;缓存会很高兴地告诉客户端列表的第 12 版是第 7 版,反之亦然。

于 2016-06-29T15:23:33.963 回答
1

回答我自己的问题。我的第一个要点可能是基于意见的,正如已经指出的那样,我在一篇文章中提出了很多问题。尽管如此,这里是其他人(VoiceOfUnreason)回答的摘要和我自己的额外研究:

ETag 是 HTTP 的资源“哈希”。它们可以与 If-Match 标头组合以具有版本控制系统。但是,ETag-headers 通常不用于声明正在创建 (PUT) 或更新 (POST/PATCH) 的资源的 ETag。存储资源的服务器通常确定 ETag。我没有发现任何明确禁止这样做的东西,但是许多实现可能会假设服务器确定 ETag 并在提供 PUT 或 PATCH 时感到困惑。

单独的修订资源是用于版本控制的 ETag 的有效替代方案。此资源必须与作为其修订版的资源同时更新。

在 HTTP 级别上,提交/回滚事务在语义上是不可强制执行的,除非通过将事务本身建模为 ReST 资源,这会使事情变得更加复杂。

但是,PATCH 的某些属性允许它用于此目的:

  • HTTP PATCH 必须是原子的,并且可以对多个资源进行操作。RFC 5789
    • 服务器必须以原子方式应用整个更改集,并且从不提供(例如,在此操作期间响应 GET)部分修改的表示。如果无法成功应用整个补丁文档,则服务器不得应用任何更改。
    • PATCH 方法会影响 Request-URI 标识的资源,也可能对其他资源产生副作用;即,可以通过应用 PATCH 创建新资源或修改现有资源。PATCH 既不安全也不幂等
  • JSON PATCH 可以由对多个资源的多个操作组成,并且必须全部应用或不应用任何操作,使其成为隐式事务。RFC 6902
    操作按照它们在数组中出现的顺序依次应用。

因此,修订可以建模为单独的资源,并且仍然可以同时更新。查询当前版本是一个简单的 GET。提交事务是一个单一的 PATCH 请求,首先包含修订测试,然后是对资源的操作,最后是更新修订资源的操作。

服务器仍然可以选择将修订发布为主要资源的 ETag。

于 2016-07-17T13:44:13.267 回答