HTTP 协议中有POST
和PATCH
方法用于部分更新。JSON API的PATCH
方法有RFC 5789、RFC 6902、RFC 7396。
但我有一个特定于大型资源的问题,需要在相当复杂的条件下进行部分更新。
假设我们有一个 API 端点"/members"
,它类似于一个包含数千个条目的大型集合,其中每个条目都有几十个字段。因此,客户端通过分页方法部分读取这些值,"GET /members?fields=f1,f2,f3"
并且每个客户端仅读取对其当前任务至关重要的字段子集。
为了简化问题,我们假设任何条目都有一个"email"
用于标识的必填字段和几个可选字段。
{
"email": "user@organization.com",
"optional1": "x",
"optional2": "x",
"optional3": "x"
}
我们有两个客户并行工作以批量创建和更新新记录。但是每个客户都希望只覆盖他们的可选字段子集。
因此,客户端 A 将发送一个包含A
批量补丁值的请求,仅允许覆盖"optional1"
字段值,因为该字段对其任务至关重要。并且该客户端还将"optional2"
为新条目的字段指定一个值。那个客户根本没有指定该"optional3"
字段。
{
"email": "user@organization.com",
"optional1": "A",
"optional2": "A"
}
虽然该记录是新的,但它已完全写入存储。然后,客户端 B 将发送一个包含B
批量补丁值的请求,仅允许覆盖"optional2"
字段值,因为该字段对其任务至关重要。
{
"email": "user@organization.com",
"optional1": "B",
"optional2": "B",
"optional3": "B"
}
由于记录已经存在,该记录的"optional1"
字段应该保持相同的旧值A
,因为该字段不是必需的,该"optional2"
字段值应该被新值覆盖,B
因为该字段是必需的,并且该"optional3"
字段值应该接收值B
,因为字段只是空的。动作的合并结果应如下所示。
{
"email": "user@organization.com",
"optional1": "A",
"optional2": "B",
"optional3": "B"
}
因此,行为取决于记录的存在。你们将如何在 JSON API 可重用端点方法中实现这一点,以便在具有复杂条件的情况下并发并行部分更新此类集合,如果该记录已经存在并包含一些值,那么该 API 的客户端将只覆盖记录中的字段子集?
RFC 6902解决方案不匹配,因为它们的和add
操作replace
实际上都替换了一个值。
add - 如果目标位置指定了一个确实存在的对象成员,则替换该成员的值。
替换- “替换”操作将目标位置的值替换为新值。操作对象必须包含一个“值”成员,其内容指定替换值。
您可以看到它与 Java Map.put()方法的匹配程度。
如果映射先前包含键的映射,则旧值将替换为指定值。
但是缺少的功能与 Java Set.add()方法相匹配。
如果指定元素尚不存在,则将其添加到此集合中(可选操作)。更正式地说,如果集合不包含元素 e2,则将指定的元素 e 添加到此集合中,使得 (e==null ? e2==null : e.equals(e2))。如果该集合已包含该元素,则调用将保持该集合不变并返回 false。
您将如何创建一个 JSON API,以允许对包含数十个可选字段的元素的非常大集合的批量补丁进行两种类型的操作?
更新
感谢@chad-jensen 提出属性优先级规则的想法。提到的文档提供了非常灵活的方法,因此我尝试根据我的需要对其进行调整。我想我会有两个操作,“初始化”优先级较低,“添加”或者更确切地说“替换”优先级更高。
那么,在我的示例中,客户端 A 和客户端 B 发送的 JSON 数据如下所示。
Client 发送的数据A
,更新"optional2"
优先级较低的字段并更新"optional1"
优先级最高的字段。
{
"email": "user@organization.com",
"initialize":
{
"optional2": "A"
},
"replace":
{
"optional1": "A"
}
}
客户端发送的数据B
,更新优先级较低的"optional1"
和字段,更新优先级最高的字段。"optional3"
"optional2"
{
"email": "user@organization.com",
"initialize":
{
"optional1": "B",
"optional3": "B"
},
"replace":
{
"optional2": "B"
}
}