简体   繁体   English

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

[英]Create aggregate root in the context of another aggregate root

i'm currently struggling with the creation of instances in the ddd context. 我目前正在努力在ddd上下文中创建实例。 i have read and searched alot and sometimes thought that i have found the answer only to realize that it doesnt feel right while programming it. 我阅读和搜索了很多内容,有时以为我找到了答案,只是意识到在编写它时感觉不对。

This is my situation: 这是我的情况:

  • I have two aggregate roots Scenario and Step . 我有两个聚合根ScenarioStep I made those AR because they encapsulate related elements of the domain and each AR should be in a consistent state. 之所以创建这些AR,是因为它们封装了域的相关元素,并且每个AR都应处于一致的状态。
  • Multiple Steps can exist in the context of a Scenario . Scenario的上下文中可以存在多个Steps They can not exist on their own. 它们不能独立存在。
  • The "name/natural id" of each Step in the context of its Scenario has to be unique. Scenario中每个Step的“名称/自然ID”必须唯一。 Changes in Scenario do not automatically influence its Steps and vice versa (eg Step doesnt care if Scenario changes some descriptions or images). Scenario更改不会自动影响其Steps ,反之亦然(例如, Step不关心Scenario更改了某些描述或图像)。
  • Different Steps of a Scenario can be used, edited, etc. at the same time. 可以同时使用,编辑Scenario不同Steps

At the moment, each Step holds a reference to its Scenario by the corresponding natural identifier. 目前,每个Step都通过相应的自然标识符保留了其Scenario的引用。 The Scenario class doesnt know anything about its Steps , so it does not hold a collection with Step references. Scenario类不了解有关其Steps任何信息,因此它不保存带有Step引用的集合。

How would i create a new Step for a given Scenario ? 如何为给定Scenario创建新Step

  1. Should i load the Scenario and call something like createNewStep(...) on it? 我应该加载场景并在其上调用诸如createNewStep(...)的东西吗? That would not enforce the uniqueness constraint (that is in fact a business constraint and not a technical one), because Scenario doesnt know about its Steps . 这不会强制执行唯一性约束(实际上是业务约束,而不是技术约束),因为Scenario不知道其Steps I would probably have to go with some kind of a "disconnected domain model" then or pass a repsoitory or service to the method to perform the checks. 然后,我可能必须使用某种“断开连接的域模型”,或者将资源管理或服务传递给该方法以执行检查。
  2. Should i use a domain service that enforces the constraint, queries the repository, and finally creates and returns the Step ? 我应该使用强制执行约束,查询存储库并最终创建并返回Step的域服务吗?
  3. Should Scenario simply know about its Steps ? Scenario应该只了解其Steps吗? I think i would like to avoid this one, since that would create a ugly-to-maintain bidirectional relationship. 我认为我想避免这种情况,因为那样会造成维护丑陋的双向关系。

One could imagine other use cases like a Step shall be classified by options that are provided by the specific Scenario . 可以想象其他用例(如Step应按特定Scenario提供的选项进行分类。 In this case and if there would be no constraints regarding the "collection" of Steps , i would probably go with the first "solution". 在这种情况下,如果对Steps的“集合”没有任何约束,则我可能会选择第一个“解决方案”。 Then again: if the classification is changed afterwards, the access to the scenario would be necessary to check for the allowed classifications. 再说一遍:如果之后更改了类别,则必须访问场景以检查允许的类别。 That brings me to a possible 4th solution: 这给我带来了第四个可能的解决方案:

  1. Using some kind of "combination" of some possible solutions. 使用某种可能的解决方案的“组合”。 Would it be a good idea to create the domain service (accessing everything needed) and use it as an argument of the method that needs it? 创建域服务(访问所需的所有内容)并将其用作需要该方法的参数的一个好主意吗? The method would then call the service where needed and the "domain logic" stays in the entity/model. 然后,该方法将在需要的地方调用服务,并且“域逻辑”保留在实体/模型中。

Thank you in advance! 先感谢您!


I'll just edit instead of copy paste answering ;) 我将只编辑而不是复制粘贴答案;)

Thank you all for your responses! 谢谢大家的回应! :) :)

Pushing the steps back into the scenario would lead to some pretty big objects which i'm trying to avoid (the current running application really suffers from this). 将步骤推回场景中会导致一些非常大的对象,而我试图避免这些对象(当前正在运行的应用程序确实遭受了此苦)。 It seems that its pretty much alike the Scrum-Example of Vaughns "Effective Aggregate Design" where he is using DomainServices to get smaller aggregates (i really dont know why i'm so uncertain about using domain services). 似乎这与Vaughns的Scrum示例非常相似,“沃森(Effective Aggregate Design)”正在使用DomainServices来获取较小的聚合(我真的不知道为什么我对使用域服务如此不确定)。 Looks like i'll have to use domainservices or split the aggregates up into "StepName" and "StepDetails" as suggested. 看起来我将不得不使用domainservices或按照建议将聚合拆分为“ StepName”和“ StepDetails”。

For background, you should read what Greg Young has to say about set validation (via WaybackMachine ). 对于背景知识,您应该阅读Greg Young关于设置验证的评论 (通过WaybackMachine )。 In particular, you really need to evaluate, in the context of your solution, what is the business impact of having a failure? 特别是,您确实需要在解决方案的上下文中评估发生故障的业务影响是什么?

Accept the failure and escalate is by far your easiest option. 接受失败并升级是您最简单的选择。 In what follows, I assume that the business impact of the failure is large, so we need to prevent it from happening. 在下面的内容中,我假设故障对业务的影响很大,因此我们需要防止故障的发生。

The "name/natural id" of each Step in the context of its Scenario has to be unique 场景中每个步骤的“名称/自然ID”必须唯一

That's a classic set validation concern. 这是经典的集合验证问题。

The first thing to do is challenge the assumptions in your model 首先要做的是挑战模型中的假设

Is your model the book of record for "name"? 您的模型是“名称”的记录簿吗? If your model isn't the authority, you have to be very cautious about introducing constraints. 如果您的模型不是权威,则在引入约束时必须非常谨慎。 Understanding the boundaries of your model's authority is really important. 理解模型权限的边界非常重要。

Is there an invariant that couples the name of a step to any other part of its state? 是否存在将步骤名称与其状态的其他部分耦合的不变式? Aggregate design discipline says that two pieces of state coupled by an invariant need to be in the same aggregate, but its silent about properties that don't participate in an invariant. 总体设计学科说,由不变式耦合的两个状态必须处于同一集合中,但是它对不参与不变式的属性保持沉默。

Is it reasonable to reject a name change while accepting other changes to a step? 在接受步骤的其他更改的同时拒绝名称更改是否合理? This is really a variation of the previous -- can tasks be split into two different commands (one involving name, one not) that can succeed or fail independently? 这实际上是前一个版本的变体-可以将任务分为两个可以独立成功或失败的不同命令(一个涉及名称,一个不涉及名称)吗?

In short, the invariant may be telling you that "step name", as a piece of state, belongs in the scenario aggregate rather than in the step aggregate. 简而言之,不变量可以告诉您“步骤名称”作为一种状态属于方案汇总而不是步骤汇总。

If you think about the problem from the perspective of a relational model, we're looking at a tuple (scenarioId, name, stepId), and the constraint says that (scenarioId, name) form a unique key. 如果您从关系模型的角度考虑问题,我们正在查看一个元组(scenarioId,name,stepId),并且约束条件表明(scenarioId,name)构成一个唯一键。 That's a hint that step name belongs to the scenario. 这暗示步骤名称属于场景。 In code, that signature looks like a scenario data structure that includes a Map<ScenarioName, ScenarioId> . 在代码中,该签名看起来像一个方案数据结构,其中包括Map<ScenarioName, ScenarioId>

That won't necessarily solve all of your problems of course, but it is a step toward aligning the model with your actual business. 当然,这不一定能解决您所有的问题,但这是使模型与实际业务保持一致的一步。

When that doesn't work... 如果那不起作用...

The "real" answer is to move the step entity back into the scenario aggregate. “真实”的答案是将步骤实体移回到方案汇总中。 One way to think about it is this -- all of the entities taken together form "the model" that we are keeping consistent. 一种思考的方式是-所有实体合在一起形成我们保持一致的“模型”。 The aggregates aren't part of the business, per se; 本质上,聚合不是业务的一部分; they are artificial, independent subdivisions within the model -- we identify and isolate aggregates as a performance optimization ; 它们是模型中的人为的,独立的细分-我们将集合体识别并隔离为性能优化 we can perform concurrent edits, and evaluate the validity of a command while loading a much smaller data set. 我们可以执行并发编辑,并在加载小得多的数据集的同时评估命令的有效性。

If the failures make the performance optimization too expensive, you take it out. 如果故障使性能优化过于昂贵,则将其淘汰。 So you can see that we have an estimate, of sorts, for what it means that the business impact is "large"; 因此,您可以看到,我们对业务影响是“巨大的”有某种估算。 it needs to be bigger than the savings we get from using aggregates on the happy path. 它需要大于我们在快乐的道路上使用聚合所获得的节省。

Another possibility is to shift where you enforce the invariant. 另一种可能性是转移您执行不变式的位置。 Relational databases are really really good at set validation. 关系数据库确实非常擅长于集合验证。 So maybe the right answer is to split the enforcement concern: put the invariant into your schema as a constraint, and ignore that constraint in code. 因此,也许正确的答案是拆分强制性问题:将不变量作为约束放入您的模式,并在代码中忽略该约束。

This isn't ideal for a number of reasons -- you've effectively "hidden" the constraint, you've introduced a constraint on the kind of data store that you use for your aggregates, you've introduced a constraint that requires that you store your step aggregates in the same database as the scenario they belong to, and so on. 由于多种原因,这不是理想的选择-您已经有效地“隐藏”了约束条件,对用于聚合的数据存储类型引入了约束条件,引入了要求满足以下条件的约束条件:您将步骤聚合与其所属的场景存储在同一数据库中,依此类推。 If you squint, you'll see that this is really just the "make the step entities part of the scenario" solution, but in disguise. 斜视一下,您会发现这实际上只是“使步骤实体成为场景的一部分”解决方案,但是变相了。

But keep in mind: part of the point of is that we can push back on the business when the code is telling us that the business model itself is wrong . 但是请记住: 在于,当代码告诉我们业务模型本身是错误的时,我们可以推迟业务。 Where's the cost benefit analysis? 成本效益分析在哪里?

Here's the thing about uniqueness constraints: the model enforces uniqueness, not correctness . 这是关于唯一性约束的事情:模型强制唯一性,而不是正确性 Imagine a data race, two different commands that each claim the same "name" for a different step in the scenario -- perhaps caused by a data entry error. 想象一下数据争用,两个不同的命令在场景中的不同步骤中各自具有相同的“名称”,这可能是由于数据输入错误引起的。 The model, presumably, can't tell which command is "right", so it's going to make some arbitrary guess (most likely, first command wins). 该模型大概无法分辨哪个命令是“正确的”,因此它将做出一些任意猜测(很可能是第一个命令获胜)。 If the model guesses wrong, it has effectively blocked the client that provided correct data! 如果模型猜错了,则它有效地阻止了提供正确数据的客户端!

In cases where the model is the authority, uniqueness constraints can make sense -- the SeatMap aggregate can enforce the constraint that only one ticket can be assigned to a seat at any given time, because it is the authority for assignment. 在模型是权限的情况下,唯一性约束可能很有意义-SeatMap聚合可以强制执行约束,即在任何给定时间只能将一张票证分配给一个座位,因为这是分配权限。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM