如何修补聚合根

时间:2017-09-22 16:14:35

标签: c# rest domain-driven-design event-sourcing

我正在尝试在REST Api中实现Json PATCH来更新事件源聚合,但我正在努力做到这一点。

我不想创建Anemic Model,因此我的Aggregate Root属性是私有的。出于这个原因,我无法使用ASP.NET Json PATCH library修补我的对象。

有什么技术可以实现这一点,还是我在尝试在事件源聚合上应用补丁时做错了什么?

示例:您将如何在Greg Young SimpleCQRS example中修补InventoryItem?

1 个答案:

答案 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"
        }
    }
]