事件溯源实践疑问:能否通过成员对象事件描述父对象状态?
针对你将父对象A0及其子对象B0、B1、C0的事件分散存储在不同集合(events-A、events-B、events-C),并通过多轮查询聚合重建A0状态的方案,存在以下核心弊端与潜在陷阱:
状态重建性能损耗:重建A0需要多次跨集合查询——先拉取A0的事件以获取关联的B、C对象ID,再分别查询每个子对象的事件流。若A0关联大量子对象(如数十上百个B实例),或子对象本身有大量事件,多次数据库往返会显著增加重建耗时。此外,为保证状态的正确性,需将所有跨流事件按时间戳全局排序,这会进一步提升计算与内存开销。
数据一致性隐患:跨多个集合查询事件时,若存在并发写入操作,可能出现"读偏序"问题。例如,在你获取A0事件后、查询B0事件前,B0产生了新的状态变更事件,会导致重建出的A0状态缺失该最新变更,引发不一致。多数数据库不支持跨集合的事务性读取,难以规避这类问题。
查询与重建逻辑复杂度飙升:你需要维护A0与子对象的关联映射逻辑,处理子对象事件流缺失、部分读取失败等异常场景,还要确保所有跨流事件的顺序正确性。这会大幅增加业务代码的复杂度,提升测试与维护成本——每次新增子对象类型(如D、E),都需修改重建逻辑以适配新的事件集合。
违背聚合根边界设计原则:在事件溯源的DDD实践中,聚合根是一致性与事务的边界。若A0作为父对象,理论上所有涉及子对象的变更都应通过A0的命令发起,并将相关事件统一存储在A0的事件流中。拆分事件到不同集合后,难以在聚合层面强制执行业务规则(例如A0处于"已归档"状态时,禁止修改关联的B0)。
单一数据源原则的两难困境:你提到的两种解读本质是"无冗余"与"易查询"的矛盾:
- 若坚持"每个状态变更仅生成一个事件",则必须承担多流查询的复杂度;
- 若追求"所有事件可通过A0直接关联查询",则需在A0的事件中重复存储子对象的变更信息(或关联标识),这会导致数据冗余,且可能出现冗余信息与子对象事件不一致的情况。
长期维护与扩展风险:随着系统迭代,子对象的事件结构可能发生变更,此时需同时维护多个事件集合的版本兼容逻辑。此外,若后续需要对A0及其子对象做全局事件分析(如统计所有关联对象的变更趋势),跨多集合的查询会变得异常繁琐。
内容的提问来源于stack exchange,提问作者GRASBOCK

