聚合边界

时间:2018-06-12 10:46:21

标签: domain-driven-design cqrs event-sourcing

Can an Entity be Shared across many Aggregates?

问题

  

可以在多个聚合中共享实体吗?在蓝皮书(Evans)中,在关于聚合的第6章中,有一个实体轮胎和轮子在聚合根车中使用的例子。如果这个例子扩展到有两个聚合,Car和Truck是否可以再次使用Tire和Wheel与Truck?

答案

  

没有。原因是聚合维护不变量,如何保持不变量   聚合通知另一个实体的更改...如果没有那么你   可能有一个无效状态的聚合。

     

是的,你可能想要它,因为它是自己的软链接聚合。顺便问一下   从外面访问实体的id没问题,问题是   同一实体分为两个聚合。

     

格雷格

我的问题

从聚合中引用外部aggreagate root的标识不是问题。

假设我们拥有属于聚合根Resource的聚合根DeviceUpdateResource

{
    "id":"933be22c-6e1f-11e8-adc0-fa7ae01bbebc",
    "userId":"2bf9d69a-6e20-11e8-adc0-fa7ae01bbebc",
    "deviceId":"a6caeaea-6e1f-11e8-adc0-fa7ae01bbebc",
    ...
}

命令由唯一ID标识的用户发出。该用户是1..n Group s的成员。 Devices也属于1..n Group s。 现在我的问题是,如果我想验证执行请求的用户是否与请求所适用的设备位于同一组中,我真的需要查询不同的聚合吗?这意味着加载由聚合Devices维护的相同事件。

很明显,两个aggegates无法更新同一个实体 - 在这种情况下Device。但是聚合Resource只会加载它以验证请求。在aggerate内部执行它而不是向不同的聚合发出命令更快。

或者? :)谢谢

2 个答案:

答案 0 :(得分:1)

  

如果我想验证执行请求的用户是否与请求所适用的设备位于同一组中,我是否真的需要查询不同的聚合?

在CQRS中,根据定义,不会查询写入模型。在DDD中,写模型是聚合。 Aggregate不应该依赖(使用)它不拥有的数据。此规则强制我们将代码放在它所属的位置,以检查正确的Aggregate中的不变量。在CQRS中,"数据"是(域)事件。

  

这意味着加载由聚合设备维护的相同事件

这也意味着一个聚合使用"数据"来自另一个聚合,所以不要这样做。

业务不变a user may only update the devices from the same group as him似乎属于不同的有界上下文,即授权。这意味着您的当前聚合边界无法以强一致的方式强制执行此规则。仅在单个Aggregate实例内保证了强一致性。在这种情况下,用户总是有可能更新他不应该的设备。无法安全地防止这种情况。因此,您需要设计系统,使其从这种情况中恢复。

从中恢复的一种方法是让一个Saga / Process管理器监听相关事件并发送补偿命令,使系统在有效状态下恢复为一个洞(它将恢复无效命令带来的修改)。 / p>

作为优化,为了限制无效情况(它们仍然会发生,但频率较低),您可以拦截用户发送给设备的命令并拒绝那些无效的命令。这种机制实际上是授权检查。在命令到达Aggregate之前,完成授权检查。您仍需要使用Saga,以防某些用户同时从设备组中删除它将命令发送到设备。

答案 1 :(得分:0)

  

现在我的问题是,如果我想验证执行请求的用户是否与请求所适用的设备位于同一组中,我是否真的需要查询不同的聚合?

排序。

聚合的基本规则是聚合负责改变自己的状态。他们用来改变自己状态的信息是他们之前的状态,以及作为参数传递给他们的信息。

因此,如果您想查询域,要知道例如用户是否是正确组的成员,那么您通常会将查询传递给Resource作为命令的参数之一。

传递查询的常用模式是"域名服务" - 我们将传递给支持某些查询的聚合接口,并为聚合负责实现它。

Resource::updateResource(Command c, PermissionsService p) {
    if (p.hasPermission(c.userId, c.deviceId)) {
        ...
    }
}

PermissionsService的一个简单实现可能只是将所需的状态从存储库中拉出来。

  

在aggerate中内部执行此操作会更快,而不是向不同的聚合发出命令。

我怀疑有些情况下,有效的数据加载和存储不会沿着整齐的聚合边界下降。我确定您应该将这些问题保留在聚合本身的域逻辑之外,但这并不意味着您无法用更有效的方式替换加载策略。

我这样想:PermissionsService需要访问Device和Groups之间关系的副本。我们并未修改这些关系,因此实际上我们只是使用缓存副本。这意味着我们可以自行决定使用我们希望有用的表示来缓存缓存,然后将该缓存移交给域逻辑以进行实际工作。

旁注:一旦您开始考虑缓存数据,您就会开始认识到您对其他聚合的调用可能是时间查询。 "现在"是主观的,但是当你的时钟表示时间t" ISN'吨。所以你的查询代码可能看起来像

Resource::updateResource(Command c, PermissionsService p) {
    Time time = ... 

    if (p.hasPermission(c.userId, c.deviceId, time)) {
        ...
    }
}

在查询中使用三个状态逻辑

  • 我可以访问当时有效的表示,答案是肯定的
  • 我可以访问当时有效的代表,答案是否
  • 我无权访问当时有效的陈述