引言
在领域驱动设计(DDD)中,我们通过聚合(Aggregate)来封装业务规则、约束和不变量,从而确保模型在内存中的一致性。但在实际开发中,我们常常忽略了一个关键问题:
模型虽然在内存中是一致的,但一旦进入持久化阶段,这种一致性很容易被破坏。
本文将分享一种创新的机制,如何让"持久化一致性"成为模型自身的责任,从而提升系统的一致性保障能力,并在开发阶段即可暴露问题,避免风险后置。
一、传统方式的两大痛点
传统做法往往依赖如下手段保障一致性:
-
事务边界(例如使用
@Transactional
) -
编程约定(在一个方法中显式依次调用所有 save)
这种方式存在两大痛点:
1. 缺少模型自身对一致性责任的表达
聚合内的多个组成部分(如主实体和其子实体)依赖外部调用者去"记得"一起保存,模型本身无法验证是否完整持久化。
2. 无法在开发阶段捕捉遗漏
如果某个保存调用被漏掉,往往只能在运行时出现数据错乱,甚至是线上事故。IDE 和编译器无法帮我们发现这种遗漏。
二、从内存一致到持久化一致:设计动机
事实上,如果你在使用 DDD,那么模型在构建阶段的一致性是天然具备的------我们用构造器、工厂方法、不可变对象等手段来保障。
但是:
模型建得再好,一旦 "出站" 被分解成多个
save()
,就容易"各自为政"。
于是我们思考:能否把模型的"一致性语义"扩展到持久化阶段?
三、模型驱动的持久化一致性方案
我们引入一种机制:
java
public void consistencyPersist(Consumer<PersistContext> persistLogic) {
PersistContext ctx = new PersistContext(this, subEntity1, subEntity2...);
persistLogic.accept(ctx);
ctx.verifyConsistency();
}
实现关键点:
-
PersistContext
提供字段访问方法(如goodsSnapshot()
、skuSnapshots()
),每次调用都会自动记录访问痕迹。 -
verifyConsistency()
会对比"应访问字段"和"实际访问字段",抛出异常提示遗漏。 -
支持 IDE 自动提示和快速补全,显著提升开发体验。
四、支持嵌套聚合一致性
每个聚合内部可以只关心下一层的一致性持久化调用:
java
// GoodsReview 聚合的持久化
@Transactional
public void persist(GoodsReview goodsReview) {
goodsReview.consistencyPersist(ctx -> {
persist(ctx.goodsSnapshot());
save(ctx.goodsReview());
});
}
// GoodsSnapshot 聚合的持久化
@Transactional
public void persist(GoodsSnapshot goodsSnapshot) {
goodsSnapshot.consistency(ctx -> {
save(ctx.goodsSnapshot());
save(ctx.skuSnapshots());
});
}
这样可以实现一致性语义在多层聚合之间的递归传播。
五、总结:让一致性更可靠、更靠前
这套机制的核心思想是:
一致性不应依赖调用者"记得",而应由模型"主张"。
通过模型驱动的持久化一致性机制,我们实现了:
-
一致性责任下沉到模型,表达更清晰
-
开发期即可捕捉遗漏,减少线上风险