聚合边界

Aggregate boundaries

Can an Entity be Shared across many Aggregates?

问题

Can an entity be shared across many aggregates? In the Blue Book (Evans), in chap 6 on aggregates there is an example of entities Tire and Wheel being used in the aggregate root Car. If this example was extended to have two aggregates, Car and Truck would it be acceptable to use Tire and Wheel again with Truck?

回答

No. The reason why is the aggregate maintains invariants, how would one aggregate notify the other on change of an entity... If it didn't then you could possibly have an aggregate in an invalid state.

Yes likely you want it as it's own aggregate with soft links. Btw there is no problem accessing the id of an entity from outside, the problem is the same entity being in two aggregates.

Greg

还有我的问题

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

假设我们有属于聚合根 Device 的聚合根 ResourceUpdateResource

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

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

维护的事件相同的事件

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

或者? :) 谢谢

if I want to verify if the user who did the request is in the same group as the device to which request applies, do I really need to query different aggregate?

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

That would mean load same events as are maintained by aggregate Devices

这也意味着一个聚合使用另一个聚合的 "data",所以不要这样做。

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

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

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

Now my question is, if I want to verify if the user who did the request is in the same group as the device to which request applies, do I really need to query different aggregate?

有点。

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

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

传递查询的通常模式是 "domain service" - 我们将向聚合传递一个支持某些查询的接口,并免除聚合的责任来实现它。

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

PermissionsService 的简单实现可能只是从存储库中提取所需的状态。

It's faster to do it internally in the aggerate rather then issuing command to different aggregate.

我怀疑在某些情况下,数据的有效加载和存储不会落在整齐的聚合边界上。我确信您应该将此类问题排除在聚合本身的域逻辑之外,但这并不意味着您不能用更高效的方法替换加载策略。

我是这样想的:PermissionsService 将需要访问 Device 和 Groups 之间关系的副本。我们没有修改这些关系,所以实际上我们只是在使用缓存的副本。这意味着我们可以根据自己的判断,在缓存中植入我们期望有用的表示,然后再将该缓存移交给域逻辑以进行实际工作。

旁注:一旦您开始考虑缓存数据,您就会开始认识到您对其他聚合的调用可能是时间 查询。 "Now" 是主观的,但 "when your clock said time t" 不是。所以你的查询代码可能真的像

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

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

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

  • 我可以访问当时有效的表示,答案是肯定的
  • 我访问了当时有效的表示,答案是否定的
  • 我无法访问当时有效的表示