在Event Sourcing/CQRS中如何处理静态数据?
在Event Sourcing/CQRS中处理静态数据的正确姿势
先明确核心原则:事件的本质是记录「业务决策发生时的上下文事实」,而非存储完整的关联静态数据;读模型的设计则完全服务于业务查询的需求。下面逐个解答你的困惑:
1. 命令端:静态数据是否该纳入事件?
不需要把完整的静态实体(比如整个Product记录,包括关联的分类、供应商)塞进事件里,但要把决策发生时依赖的、会影响业务逻辑或后续读模型展示的关键静态数据快照加进去。
- 比如
LineAdded事件里的itemPrice就是典型的快照:产品价格后续可能调整,但订单里的价格必须是下单时的价格,所以必须固化到事件中。 - 如果业务要求旧订单展示「下单时的产品描述」,那就要把
itemDescription也放进事件;但如果业务允许旧订单自动同步最新的产品描述,那就不用加。 - 分类、供应商这类关联数据,只有当它们是当时决策的依据(比如下单时限制只能选某类供应商的产品),或者读模型需要展示历史状态时,才需要把当时的快照(比如
categoryName、supplierName)放进事件,否则完全没必要。
2. 查询端:静态数据该进读模型还是查询时JOIN?
完全取决于业务需求:
- 如果业务要求查询结果展示静态数据的最新状态(比如产品描述改了,所有旧订单的展示都要同步更新),那读模型只需要存静态数据的ID(比如
itemID),查询时直接JOIN静态表即可。这种方式不用处理静态数据变更的同步问题,若担心性能,可通过缓存静态数据(如Redis)优化。 - 如果业务要求查询结果展示数据发生时的历史状态(比如旧订单必须显示下单时的产品描述和价格),那必须把当时的静态数据快照放进事件,投影时直接写入读模型,查询时不需要JOIN。这种方式能精准还原历史,查询效率也更高。
3. 不纳入事件就必须JOIN?
没错,但这不是问题——JOIN是数据库的常规操作,只要业务允许展示静态数据的最新状态,这种方式反而更简洁,避免了事件数据冗余。
4. 纳入事件后静态数据变更无法反映?
这是有意为之的取舍。如果业务要求历史记录必须保持当时的状态,那这正是你想要的效果;如果业务要求历史记录同步最新静态数据,那你就不该把静态数据塞进事件,而是用JOIN的方式。
总结下来,正确的处理方式分两种场景:
- 场景1:静态数据变更需要同步到所有历史记录:
- 命令端:事件只存静态数据的ID,不存快照
- 查询端:读模型存ID,查询时JOIN静态表(或缓存)
- 场景2:历史记录必须保留静态数据的当时状态:
- 命令端:事件存入决策时依赖的关键静态数据快照(不用全量,只存需要展示或后续业务逻辑需要的字段)
- 查询端:投影时把快照写入读模型,查询时直接读取,无需JOIN
内容的提问来源于stack exchange,提问作者user1102018
相关产品推荐
相关产品推荐

