如何更新与另一个聚合有关联的聚合的 ReadModel

How to update ReadModel of an Aggregate that has an association with another Aggregate

我正在尝试分离读取和写入模型。总之,我有这 2 个实体,它们之间有关联:

//AgregateRoot
class ProfessionalFamily {
    private ProfessionalFamilyId id;
    private String name;
}

//AgregateRoot
class Group {
    private GroupId id;
    private String literal;
    private ProfessionalFamilyId professionalFamilyId; //ManyToOne association referenced by the ID of "professional-family"
}

我用于 return 网格中数据的读取模型是下一个。

class GroupReadModel {
    private String id;
    private String groupLiteral;
    private String professionalFamilyName;
}

我想将 NoSql 用于 ReadModel 查询,并将它们分开用于写入模型。但我头疼的是:使用这种方法,当创建一个组时,我会触发一个事件(GroupCreated),一个事件处理程序会监听该事件并将 Read/View/Projection 模型存储在 NoSql 数据库中。所以我的问题是:如果我需要更新 ProfessionalFamilyName 并且这与多个相关,例如 1000 个组(还有更多组),我如何更新 ReadModel 中与我的 professionalFamily 相关的所有组更新了吗?估计是我什么都做不好。

非常感谢。

NoSql 数据库通常不支持数据规范化,甚至有意打破这一概念。如果您使用关系数据库系统,您通常会规范化您的数据,并且对于每个组,您将只存储 ProfessionalFamily 的 ID,而不是在每个组文档中复制 ProfessionalFamily 的名称。所以一般来说,NoSql 的数据库复制是可以接受的。

但我认为在决定使用 NoSql 或关系数据库之前,您应该(至少)考虑以下几点:


读取速度与写入速度的优先级:

如果你需要你的写入(在你的情况下更改名称)非常快,因为它们经常发生并且读取速度的优先级较低,那么 NoSql 可能不是最佳选择。您仍然可以研究 MongoDB 等技术,它提供了某种混合方法并允许在一定程度上对数据进行规范化和索引。

在关系数据库中具有规范化结构时,写入通常会更快,而在 NoSql 数据库中没有规范化和复制的情况下,读取通常会更快。但这当然取决于您正在比较的手头技术以及我们正在谈论的实体数量(在您的情况下为组)以及 cross-referenced 数据量。如果由于规范化而需要在读取期间进行大量连接,则与组文档相比,读取性能通常会更差,组文档由于重复而所有必需的数据都已经存在。


控制数据structure/schema

如果您知道数据的外观,您可能不需要 NoSql 数据库的优势,它非常适合经常更改或您无法控制的数据结构。如果情况并非如此,您可能无法从 NoSql 技术中获益。


此外还有一件事需要考虑:你读取的模型数据的一致性如何?当您采用某种 event-sourcing 方法时,我猜您已经接受了 最终一致性 。这意味着不仅事件处理是异步执行的,而且您也可以接受这一点 - 回到您的示例 - 并非所有组都同时更新为新的姓氏,而是异步或通过某些后台作业更新,如果它是一个组仍然显示旧名称而其他组已经显示新名称已有一段时间了,这不是问题。


Most probably I'm not doing anything well.

你没有做错任何事或做对 per-se 选择这种方法,只要你出于正确的理由(包括这些考虑因素)决定使用 NoSql(或反对)。

我和我的团队最近讨论了一个类似的场景,我们通过将 CRUD 方法更改为 DDD 方法来解决它。这是一个例子:

给定一位旅行者,其中列出了他去过的目的地。

如果我有一个 destinationUpdated 这样的事件,那么我应该像你说的那样循环访问每个旅行者,但这有意义吗?从用户的角度来看,destinationUpdated 意味着什么?没有什么!您应该找到真正的用户意图。

如果旅行者在输入访问目的地时输入错误,那么您的事件应该是 travelerCorrectedDestination,这可以解决问题,因为 travelerCorrectedDestination 现在包含旅行者 ID,因此您不必遍历所有不再是旅行者了。

通过应用 DDD 方法,问题通常由他们自己解决。