首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在并发编辑环境中对子实体强制不变量

在并发编辑环境中对子实体强制不变量
EN

Stack Overflow用户
提问于 2017-02-22 05:12:52
回答 3查看 175关注 0票数 1

考虑到子集合不能超过x个项的不变量,域如何保证在并发/web环境中强制执行这样的不变量?让我们看一个(经典的)示例:

我们有一个带有Manager s的Employee,(假设的)不变式声明一个管理器不能有超过七个直接报告(Employees)。我们可以这样(天真地)实现这一点:

代码语言:javascript
复制
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();
        }
    }
}

直到最近,我还认为这种方法是足够好的。然而,似乎存在一个边缘情况,使数据库能够存储超过7个雇员,从而打破不变。考虑一下这一系列事件:

  1. Person A转到UI中的编辑管理器 (内存中有6名雇员,数据库中有6名雇员)
  2. Person B转到UI中的编辑管理器 (内存中有6名雇员,数据库中有6名雇员)
  3. Person B添加员工并保存更改 (内存中有7名雇员,数据库中有7名雇员)
  4. Person A添加员工并保存更改 (内存中有7名员工,数据库__中有8名员工)

当域对象再次从数据库中提取时,Manager构造函数可能(或不可能)增强集合上的Employee计数不变量,但无论哪种方式,我们现在的数据和我们的不变量期望之间都有差异。我们如何防止这种情况发生?我们怎样才能从这种干净的环境中恢复过来?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-02-22 06:20:42

考虑一下这一系列事件:

代码语言:javascript
复制
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先决条件失败

对此的一个改进是为您的域使用更好的模型。Udi Dahan

时间上的微秒差异不会影响核心业务行为。

具体来说,如果您的模型因为命令A和B碰巧以不同的顺序处理而处于不同的状态,那么您的模型可能与您的业务不太匹配。

您的示例中的类似之处是,这两个命令都应该成功,但第二个命令也应该设置一个标志,指出聚合当前不符合要求。这种方法可以防止当addEmployee命令和removeEmployee命令在传输层中被错误地排序时的愚蠢行为。

(假设的)不变式指出,Manager不能有超过7个直接报告

值得警惕的是--即使在假设的例子中,数据库是否是记录的书。数据库很少获得对现实世界的否决权。如果现实世界是记录的书,你可能不应该拒绝改变。

票数 5
EN

Stack Overflow用户

发布于 2017-02-22 07:14:57

我们如何防止这种情况发生?

Repository实现中实现此行为:加载Aggregate时,还要跟踪Aggregate's版本。该版本可以作为Aggregate's Id和整数序列号的唯一密钥约束来实现。每个Aggregate都有自己的序列号(最初,每个Aggregate都有序列号0)。在Repository尝试持久化之前,它会增加序列号;如果发生了并发持久化,Repository后面的数据库将抛出“违反的唯一键约束”类型的异常,并且不会发生持久化。然后(如果您按照DDD!中应该做的那样将聚合设计为一个纯的、无副作用的对象),您可以透明地重试命令执行,重新运行聚合的所有域代码,从而重新检查不变量。请注意,只有在发生“唯一约束冲突”基础结构异常时,才必须重新尝试该操作,而不是在Aggregate抛出域异常的情况下。

我们怎样才能从这种干净的环境中恢复过来?

您可以重试命令的执行,直到没有引发“唯一的约束违反”。我在PHP中实现了这个重试:https://github.com/xprt64/cqrs-es/blob/master/src/Gica/Cqrs/Command/CommandDispatcher/ConcurrentProofFunctionCaller.php

票数 2
EN

Stack Overflow用户

发布于 2017-03-01 09:00:54

与其说这是DDD问题,不如说是持久性层问题。有多种方法来看待这个问题。

从传统酸/强稠度角度看

您需要查看特定数据库的可用并发和隔离策略,这可能反映在您的ORM功能中。其中一些将允许您检测此类冲突,并在Person A在步骤4保存更改时抛出异常。

正如我在我的评论中所说的,在一个典型的web应用程序中,使用Unit模式(通过ORM或其他方式),这种情况不应该像你似乎暗示的那样频繁发生。实体不会一直停留在由UoW跟踪的内存中(步骤1到步骤4 )。它们在步骤3和步骤4重新加载。事务3和4必须是并发的,才会发生问题。

更弱,无锁的一致性

你有几个选择。

  • 最后一次获胜,来自A人的7名员工将从个人B中删除这些人。这在特定的商业环境中是可行的。您可以通过将更改保持为employees = <new list>而不是employees.Add来完成。
  • 依赖版本号,如@VoiceOfUn情理所述。
  • 最终与补偿的一致性,即应用程序中的其他内容在Person A和B的事务之外检查事实后不变的(employees.Count() < 7)。如果检测到违反规则的情况,则必须采取补偿操作,例如回滚最后一次操作,并通知Person A经理可能工作过度。
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42383214

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档