在另一个聚合根的上下文中创建聚合根

Create aggregate root in the context of another aggregate root

我目前正在为在 ddd 上下文中创建实例而苦苦挣扎。 我已经阅读和搜索了很多,有时以为我找到了答案只是为了意识到它在编程时感觉不对。

这是我的情况:

目前,每个 Step 通过相应的自然标识符持有对其 Scenario 的引用。 Scenario class 对其 Steps 一无所知,因此它不包含具有 Step 引用的集合。

我如何为给定的 Scenario 创建一个新的 Step

  1. 我应该加载场景并调用类似 createNewStep(...)就可以了吗?那不会强制唯一性 约束(实际上是业务约束,而不是 技术性的),因为 Scenario 不知道它的 Steps。我可能不得不使用某种 "disconnected domain model" 然后将 repsoitory 或服务传递给执行检查的方法。
  2. 我是否应该使用域服务来强制执行约束、查询存储库并最终创建并returns Step
  3. Scenario 应该只知道它的 Steps 吗?我想我想避免这个,因为这会造成难以维护的双向关系。

可以想象像 Step 这样的其他用例应该 class 由特定 Scenario 提供的选项来化。在这种情况下,如果没有关于 Steps 的 "collection" 的限制,我可能会选择第一个 "solution"。话又说回来:如果 classification 之后发生更改,则需要访问场景以检查允许的 classifications。这让我想到了第 4 种可能的解决方案:

  1. 使用某种 "combination" 的一些可能的解决方案。创建域服务(访问所需的一切)并将其用作需要它的方法的参数是否是个好主意?然后该方法将在需要时调用服务,并且 "domain logic" 保留在 entity/model.

提前致谢!


我只是编辑而不是复制粘贴回答;)

谢谢大家的回复! :)

将步骤推回到场景中会导致一些我试图避免的相当大的对象(当前的 运行 应用程序确实受此影响)。看起来它与 Vaughns "Effective Aggregate Design" 的 Scrum 示例非常相似,他在其中使用 DomainServices 来获得更小的聚合(我真的不知道为什么我对使用域服务如此不确定)。看来我必须使用域服务或按照建议将聚合拆分为 "StepName" 和 "StepDetails"。

关于背景,您应该阅读 Greg Young 对 set validation (via WaybackMachine) 的评论。特别是,您确实需要根据解决方案评估 失败对业务的影响是什么?

接受失败并升级是迄今为止最简单的选择。下面我假设故障对业务的影响较大,所以我们要预防它的发生。

The "name/natural id" of each Step in the context of its Scenario has to be unique

这是一个经典的集合验证问题。

首先要做的是挑战模型中的假设

你的模型是 "name" 的记录簿吗? 如果你的模型不是权威,你必须非常谨慎地引入约束。了解模特权威的界限非常重要。

是否存在将步骤的名称与其状态的任何其他部分耦合的不变量?聚合设计规则表明,由不变量耦合的两个状态需要是在同一个集合中,但它对不参与不变量的属性保持沉默。

在接受步骤的其他更改的同时拒绝名称更改是否合理?这实际上是前一个的变体 - 任务是否可以拆分为两个不同的命令(一个涉及名称,一个不涉及)可以独立成功或失败?

简而言之,不变量可能告诉你"step name",作为一个状态,属于场景聚合而不是步骤聚合。

如果你从关系模型的角度考虑问题,我们看的是一个元组(scenarioId, name, stepId),约束说(scenarioId, name)形成一个唯一键。这暗示步骤名称属于场景。在代码中,该签名看起来像一个包含 Map<ScenarioName, ScenarioId>.

的场景数据结构

当然,这不一定能解决您的所有问题,但这是使模型与您的实际业务保持一致的一个步骤。

当这不起作用时...

"real"答案是将步骤实体移回场景聚合。一种思考方式是——所有实体一起形成 "the model",我们保持一致。聚合本身不是业务的一部分;它们是模型中人为的、独立的细分——我们将聚合识别和隔离为 性能优化;我们可以执行并发编辑,并在加载更小的数据集时评估命令的有效性。

如果失败导致性能优化成本太高,你就把它去掉。因此,您可以看到我们对业务影响的含义进行了某种估计 "large";它需要大于我们在快乐路径上使用聚合所获得的节省。

另一种可能性是转移你执行不变量的地方。关系数据库在集合验证方面确实非常好。因此,也许正确的答案是拆分执行问题:将不变量作为约束放入您的模式中,并在代码中忽略该约束。

出于多种原因,这并不理想——您实际上已经 "hidden" 约束,您已经对用于聚合的数据存储类型引入了约束,您我们引入了一个约束,要求您将步骤聚合存储在与它们所属的场景相同的数据库中,等等。如果你眯着眼睛,你会发现这实际上只是 "make the step entities part of the scenario" 解决方案,但经过伪装。

但请记住: 的部分要点是,当代码告诉我们 业务模型本身错误时,我们可以推迟业务 .成本效益分析在哪里?

这是关于唯一性约束的事情:模型强制执行唯一性,而不是正确性。想象一下数据竞争,两个不同的命令在场景中的不同步骤中每个都声明相同的 "name" —— 可能是由数据输入错误引起的。该模型大概无法分辨哪个命令是 "right",因此它会进行一些任意猜测(最有可能的是,第一个命令获胜)。如果模型猜错了,就有效屏蔽了提供正确数据的客户端!

在模型是权威的情况下,唯一性约束是有意义的——SeatMap 聚合可以强制约束在任何给定时间只能将一张票分配给一个座位,因为它是 分配权限。