首页 > 解决方案 > DDD 和唯一性约束

问题描述

如何使用 DDD 验证唯一约束?假设Entity一个属性name在系统中必须是唯一的,并且有一个特定的EntityRepository方法nameExists(name): bool......这是我发现人们建议做的,因为存储库是所有Entityies集合的抽象,应该能够执行此检查。

因此,在创建/添加新Entity的命令/域服务之前,可以检查newName存储库是否存在,但我认为由于并发性,这并不总是有效。

在同时启动两个事务的并发场景中,EntityRepository'nameExists方法可能会false为两个事务返回,因此这两个具有相同名称的条目将被错误地插入。

我确信我遗漏了一些基本的东西,但是我找到的答案都指向存储库exists方法 - TBH 其他人说UNIQUE应该在数据库上设置一个约束来捕获并发情况,但是如果使用事件源或持久性怎么办没有唯一约束的层?

| 跟进问题|

如果唯一性约束要应用于层次结构的不同级别怎么办?

A在系统中必须是唯一的,然后Containers在 a 中必须是唯一的。nameChild nameContainer

假设事务数据库在尽可能低的级别处理唯一性,那么域呢?

我是否仍然应该在域级别表达唯一性逻辑,例如使用域服务来实现系统级唯一性并将实体嵌入Child实体中Container并具有业务规则(并因此制作Container聚合根)?

或者我不应该为“复制”域中的唯一性而烦恼,并且(鉴于两者之间没有其他规则适用)拆分ContainerChild?那么域会缺乏表现力吗?

标签: constraintsdomain-driven-designunique

解决方案


我确定我缺少一些基本的东西

不是基本的东西。

我们通常用于在一组实体中强制执行约束(如唯一性)的术语是集合验证。Greg Young 提醒您注意一个具体问题:

失败对业务的影响是什么

大多数集合约束属于以下两类之一

  • 当系统达到稳定状态时需要为真的约束,但在工作进行时可能不成立。在业务流程中,通常通过检测存储数据中的冲突,然后调用各种缓解流程来解决冲突来处理这些问题。

  • 必须始终为真的约束。

第一类包括双重预订飞机座位;除非两个人都出现,否则这不一定是问题,即使那样你也可以通过将某人撞到另一个座位或另一个航班来处理它。

在这些情况下,你会尽最大努力——你查看该系列的最新副本,确保那里没有冲突,然后希望最好(接受某个百分比的时间,你会错过更改) .

参见回忆、猜测和道歉(Pat Helland,2007)。

第二类是硬的;为了确保不变量成立,您必须锁定整个集合以确保比赛不允许两个不同的作者插入冲突的信息。

关系数据库往往非常擅长集合验证 - 将整个集合放入单个数据库将是正确的答案(请注意假设该集合足够小以适合单个数据库 - 尝试将两个数据库锁定在同时很难)。

另一种可能性是确保在任何给定时间只有一个编写者可以更新集合——当你是唯一一个参加比赛的人时,你不必担心会输掉比赛。

有时您可以锁定一个较小的集合——例如,想象有一组带有数字的锁,名称的哈希码告诉您必须抓住哪个锁。

这个最简单的版本是您可以将名称用作聚合标识符本身。

如果使用事件溯源或没有唯一约束的持久层?

有时,您会引入一个专用于集合的持久存储,只是为了确保您可以保持不变量。请参阅“微服务”。

但是,如果您不能更改数据库,并且您不能使用具有您需要的锁定保证的数据库,并且业务绝对必须让该集合始终有效......那么您将单线程的那部分工作。

每个想要更改名称的人都将请求放入队列中,并且负责管理不变量的一个线程验证每个更改。

没有魔法;只是努力工作和取舍。


推荐阅读