我正在尝试在REST Api中实现Json PATCH来更新事件源聚合,但我正在努力做到这一点。
我不想创建Anemic Model,因此我的Aggregate Root属性是私有的。出于这个原因,我无法使用ASP.NET Json PATCH library修补我的对象。
有什么技术可以实现这一点,还是我在尝试在事件源聚合上应用补丁时做错了什么?
示例:您将如何在Greg Young SimpleCQRS example中修补InventoryItem?
答案 0 :(得分:5)
示例:您将如何在Greg Young SimpleCQRS示例中修补InventoryItem?
你可能不会。与PATCH一样,PUT支持贫血数据存储的语义。 "让你的代表看起来像我的#34;意味着您正在控制远离远程域模型。你不应该告诉域模型下一个状态,你应该告诉它做什么。
那就是说,让我们仔细看看PATCH
PATCH方法请求将请求实体中描述的一组更改应用于Request-URI标识的资源。这组更改以称为"补丁文档"的格式表示。由媒体类型识别。
因此,在这种情况下,媒体类型将定义要应用于模型的零个或多个命令的列表的处理规则。从概念上讲,它与JSON-Patch类似,因为该文档描述了资源应用的a sequence of operations。
要明确的是,JSON-Patch不是正确使用的媒体类型;语义错了。因此,如果有人尝试JSON-Patch您的InventoryItem,那么您应该发回415 Unsupported Media类型和严厉的注释。
例如,如果您查看Event Store的HTTP文档,您会看到writing to a stream使用定制的媒体类型来描述事件:application/vnd.eventstore.events+json
。
如果你仔细看 ,你会发现使用的方法是POST,而不是PATCH。
考虑到API中的资源与模型中的聚合之间存在一些间接性,这可能是一个好主意。以下是Jim Webber如何描述它
网络不是您的域名,它是一个文档管理系统。所有HTTP谓词都适用于文档管理域。 URI不会映射到域对象 - 这违反了封装。工作(例如:向域模型发出命令)是管理资源的副作用。换句话说,资源是反腐败层的一部分。您应该希望集成域中的资源比业务域中的业务对象多得多。
资源调整您的网络域模型
所以,重要的是:如果您要使用PATCH(或PUT,那么),那么您提出的习惯用语是客户端获取资源的表示,修改该表示,然后将其返回给API,此时API需要弄清楚这些更改实际意味着什么域命令。
以库存项目为例;如果我们在模型之外,查看库存项目的表示,那么我们通常会查看a read model,因此表示可能看起来像
GET /ef4454ae-4a82-4bf2-82e1-3da9229ecc94
{
"Id": "ef4454ae-4a82-4bf2-82e1-3da9229ecc94",
"Version": 7,
"Name" : "REST in Practice",
"CurrentCount": 20
}
在资源只是文档的贫血领域,我们可以简单地通过......来更新当前计数。
PUT /ef4454ae-4a82-4bf2-82e1-3da9229ecc94
{
"Id": "ef4454ae-4a82-4bf2-82e1-3da9229ecc94",
"Version": 8,
"Name" : "REST in Practice",
"CurrentCount": 30
}
但是如果域名不是贫血,那么我们需要将对文档状态的更改重新命名为发送到模型的命令。在这种特殊情况下,变化很简单,我们需要观察CurrentCount中的不同,计算差异,使用变化的符号来识别正确的命令,以及初始化的变化幅度命令。
现在,我们注意到正在修补的表示是资源的表示,而不是库存项的表示;我们有更多的回旋余地。所以我们可以做点像......
GET /ef4454ae-4a82-4bf2-82e1-3da9229ecc94
{
"Id": "ef4454ae-4a82-4bf2-82e1-3da9229ecc94",
"Version": 7,
"Name" : "REST in Practice",
"CurrentCount": 20,
"pendingCommands" : []
}
然后一个聪明的客户可以尝试......
PUT /ef4454ae-4a82-4bf2-82e1-3da9229ecc94
{
"Id": "ef4454ae-4a82-4bf2-82e1-3da9229ecc94",
"Version": 7,
"Name" : "REST in Practice",
"CurrentCount": 20,
"pendingCommands" : [
{
"command":"CheckIn",
"count": 10
}
]
}
现在API层的工作再次简单;它只需要知道如何解析pendingCommand列表中的对象。
如果您对这种方法感到满意,那么您当然可以用PATCH替换PUT
PATCH /ef4454ae-4a82-4bf2-82e1-3da9229ecc94
Content-Type: application/json-patch+json
[
{
"op":"add",
"path":"/pendingCommands/0",
"value":
{
"command":"CheckIn",
"count": 10
}
}
]
请注意,PUT实际上并未承诺所接收的表示将可观察
给定表示的成功PUT将表明在相同目标资源上的后续GET将导致在200(OK)响应中发送等效表示。但是,不能保证这样的状态改变是可观察的,因为目标资源可能被其他用户代理并行地操作,或者在接收到任何后续GET之前可能受到源服务器的动态处理。成功的响应仅意味着用户代理的意图是在原始服务器处理时实现的。
"动态处理"在这种情况下是pendingCommands的运行。
现在,如果您仔细查看JSON-Patch,您可能会发现它只是一个定义良好的命令列表,这些命令的目标是" JSON Document"汇总,以及正确的。
因此,通过类比,您可以定义InventoryItem-Patch格式,该格式描述了对InventoryItems有效的操作列表(因此您将定义operations add / move / replace / ...而不是{}操作ChangeName / Checkin / Deactivate ...)
GET /ef4454ae-4a82-4bf2-82e1-3da9229ecc94
{
"Id": "ef4454ae-4a82-4bf2-82e1-3da9229ecc94",
"Version": 7,
"Name" : "REST in Practice",
"CurrentCount": 20
}
PATCH /ef4454ae-4a82-4bf2-82e1-3da9229ecc94
Content-Type: application/vnd.inventory-item-patch+json
[
{
"changeName" :
{
"newName" : "REST in Patch"
}
}
]