鉴于子集合不能超过 x 个项目的不变量,域如何保证在并发/网络环境中强制执行这样的不变量?让我们看一个(经典的)例子:
我们有一个经理
和员工
。 (假设的)不变量表明经理的直接下属(员工
)不能超过七个。我们可能会像这样(天真地)实现它:
public class Manager {
// Let us assume that the employee list is mapped (somehow) from a persistence layer
public IList<Employee> employees { get; private set; }
public Manager(...) {
...
}
public void AddEmployee(Employee employee) {
if (employees.Count() < 7) {
employees.Add(employee);
} else {
throw new OverworkedManagerException();
}
}
}
直到最近,我还认为这种方法已经足够好了。但是,似乎存在一种边缘情况,使数据库可以存储更多 的员工,从而打破不变量。考虑这一系列事件:
- A 去编辑 UI 中的经理
(内存中有 6 名员工,数据库中有 6 名员工) - B 去编辑 UI 中的经理
(内存中有 6 名员工,数据库中有 6 名员工) - B 添加员工并保存更改
(内存中有 7 个员工,数据库中有 7 个员工) - A 添加员工并保存更改
(内存中有 7 名员工,数据库中有 8 名员工)
当域对象再次从数据库中提取时,Manager
构造函数可能(或可能不会)加强集合上的 Employee
计数不变性,但无论哪种方式我们现在的数据与我们的不变量期望值之间存在差异。我们如何防止这种情况发生?我们如何从中彻底恢复?
最佳答案
Consider this series of events:
Person A goes to edit Manager in UI
(6 employees in memory, 6 employees in database)
Person B goes to edit Manager in UI
(6 employees in memory, 6 employees in database)
Person B adds Employee and saves changes
(7 employees in memory, 7 employees in database)
Person A adds Employee and saves changes
(7 employees in memory, 8 employees in database)
最简单的方法是将数据库写入实现为比较和交换操作。所有写入都使用聚合的陈旧副本(毕竟,我们正在查看内存中的聚合,但记录簿是磁盘上的持久副本)。关键思想是,当我们实际执行写入时,我们也在检查我们正在使用的陈旧副本是否仍然是记录簿中的事件副本。
(例如,在事件源系统中,您不会追加到流中,而是追加到流中的特定位置——即,您期望尾指针所在的位置。所以在比赛中,只有一个写入提交到尾部位置;另一个因并发冲突而失败并重新开始。)
Web 环境中的类似操作可能是使用 eTag,并在执行写入时验证 etag 是否仍然有效。获胜者获得成功响应,失败者获得 412 Precondition Failed .
对此的改进是为您的域使用更好的模型。 Udi Dahan wrote :
A microsecond difference in timing shouldn’t make a difference to core business behaviors
具体来说,如果您的模型只是因为命令 A 和 B 恰好以不同的顺序处理而最终处于不同的状态,则您的模型可能与您的业务不太匹配。
您的示例中的模拟是两个命令都应该成功,但是两个命令中的第二个还应该设置一个标志,指出聚合当前不合规。当 addEmployee 命令和 removeEmployee 命令碰巧在传输层中以错误的方式排序时,这种方法可以防止愚蠢行为。
The (hypothetical) invariant states that a Manager cannot have more than seven direct reports
即使是在假设的例子中,也要警惕的是数据库是否是记录簿。数据库很少对现实世界拥有否决权。如果现实世界是记录簿,您可能不应该拒绝更改。
关于c# - 在并发编辑环境中为子实体强制执行不变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42383214/