在另一个聚合根的上下文中创建聚合根
Create aggregate root in the context of another aggregate root
我目前正在为在 ddd 上下文中创建实例而苦苦挣扎。
我已经阅读和搜索了很多,有时以为我找到了答案只是为了意识到它在编程时感觉不对。
这是我的情况:
- 我有两个聚合根
Scenario
和 Step
。我做了那些 AR
因为它们封装了域和每个 AR 的相关元素
应该处于一致的状态。
- 多个
Steps
可以存在于
Scenario
的上下文。它们不能单独存在。
- 每个
Step
的 "name/natural id" 在其 Scenario
的上下文中必须是唯一的。 Scenario
的变化不会自动影响它的 Steps
和
反之亦然(例如 Step
不关心 Scenario
是否改变了一些
说明或图片)。
- 一个
Scenario
的不同Steps
可以同时使用、编辑等。
目前,每个 Step
通过相应的自然标识符持有对其 Scenario
的引用。 Scenario
class 对其 Steps
一无所知,因此它不包含具有 Step
引用的集合。
我如何为给定的 Scenario
创建一个新的 Step
?
- 我应该加载场景并调用类似
createNewStep(...)
就可以了吗?那不会强制唯一性
约束(实际上是业务约束,而不是
技术性的),因为 Scenario
不知道它的 Steps
。我可能不得不使用某种 "disconnected domain model" 然后将 repsoitory 或服务传递给执行检查的方法。
- 我是否应该使用域服务来强制执行约束、查询存储库并最终创建并returns
Step
?
-
Scenario
应该只知道它的 Steps
吗?我想我想避免这个,因为这会造成难以维护的双向关系。
可以想象像 Step
这样的其他用例应该 class 由特定 Scenario
提供的选项来化。在这种情况下,如果没有关于 Steps
的 "collection" 的限制,我可能会选择第一个 "solution"。话又说回来:如果 classification 之后发生更改,则需要访问场景以检查允许的 classifications。这让我想到了第 4 种可能的解决方案:
- 使用某种 "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" 解决方案,但经过伪装。
但请记住:domain-driven-design 的部分要点是,当代码告诉我们 业务模型本身错误时,我们可以推迟业务 .成本效益分析在哪里?
这是关于唯一性约束的事情:模型强制执行唯一性,而不是正确性。想象一下数据竞争,两个不同的命令在场景中的不同步骤中每个都声明相同的 "name" —— 可能是由数据输入错误引起的。该模型大概无法分辨哪个命令是 "right",因此它会进行一些任意猜测(最有可能的是,第一个命令获胜)。如果模型猜错了,就有效屏蔽了提供正确数据的客户端!
在模型是权威的情况下,唯一性约束是有意义的——SeatMap 聚合可以强制约束在任何给定时间只能将一张票分配给一个座位,因为它是 分配权限。
我目前正在为在 ddd 上下文中创建实例而苦苦挣扎。 我已经阅读和搜索了很多,有时以为我找到了答案只是为了意识到它在编程时感觉不对。
这是我的情况:
- 我有两个聚合根
Scenario
和Step
。我做了那些 AR 因为它们封装了域和每个 AR 的相关元素 应该处于一致的状态。 - 多个
Steps
可以存在于Scenario
的上下文。它们不能单独存在。 - 每个
Step
的 "name/natural id" 在其Scenario
的上下文中必须是唯一的。Scenario
的变化不会自动影响它的Steps
和 反之亦然(例如Step
不关心Scenario
是否改变了一些 说明或图片)。 - 一个
Scenario
的不同Steps
可以同时使用、编辑等。
目前,每个 Step
通过相应的自然标识符持有对其 Scenario
的引用。 Scenario
class 对其 Steps
一无所知,因此它不包含具有 Step
引用的集合。
我如何为给定的 Scenario
创建一个新的 Step
?
- 我应该加载场景并调用类似
createNewStep(...)
就可以了吗?那不会强制唯一性 约束(实际上是业务约束,而不是 技术性的),因为Scenario
不知道它的Steps
。我可能不得不使用某种 "disconnected domain model" 然后将 repsoitory 或服务传递给执行检查的方法。 - 我是否应该使用域服务来强制执行约束、查询存储库并最终创建并returns
Step
? -
Scenario
应该只知道它的Steps
吗?我想我想避免这个,因为这会造成难以维护的双向关系。
可以想象像 Step
这样的其他用例应该 class 由特定 Scenario
提供的选项来化。在这种情况下,如果没有关于 Steps
的 "collection" 的限制,我可能会选择第一个 "solution"。话又说回来:如果 classification 之后发生更改,则需要访问场景以检查允许的 classifications。这让我想到了第 4 种可能的解决方案:
- 使用某种 "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" 解决方案,但经过伪装。
但请记住:domain-driven-design 的部分要点是,当代码告诉我们 业务模型本身错误时,我们可以推迟业务 .成本效益分析在哪里?
这是关于唯一性约束的事情:模型强制执行唯一性,而不是正确性。想象一下数据竞争,两个不同的命令在场景中的不同步骤中每个都声明相同的 "name" —— 可能是由数据输入错误引起的。该模型大概无法分辨哪个命令是 "right",因此它会进行一些任意猜测(最有可能的是,第一个命令获胜)。如果模型猜错了,就有效屏蔽了提供正确数据的客户端!
在模型是权威的情况下,唯一性约束是有意义的——SeatMap 聚合可以强制约束在任何给定时间只能将一张票分配给一个座位,因为它是 分配权限。