【问题标题】:DDD: Aggregate boundary vs compositional convenience.DDD:聚合边界与组合便利性。
【发布时间】:2016-09-07 15:29:25
【问题描述】:

我正在开始一个新的 DDD 项目,但我觉得我还没有真正掌握这些概念。到目前为止,我的域中有两个聚合根,RecipeFood。他们的关系是这样的:

`Recipe`->*`Ingredient`->`Food`

Ingredient 是数量 + Food

我认为可能在 Recipe 聚合中包含聚合 (Food) 很糟糕,但它确实很方便,因为它允许通过导航关系来计算食谱的营养价值。

我也不存储计算结果,我每次都重新计算,所以如果食物发生变化,食谱会自动更新,我不知道这是否是个好主意。

另外,是否可以为Recipe+Ingredient 提供一个存储库,因为它们在同一个聚合中,我必须加载所有成分,这样我才能在配方的构造函数中传递它们?

【问题讨论】:

  • 如果您正在发明自己的项目来学习 DDD,“编辑”是一个非常糟糕的领域。寻找模型可以为自己做出决定的东西。

标签: domain-driven-design aggregate


【解决方案1】:

是否可以为 Recipe+Ingredient 提供一个存储库,因为它们在同一个聚合中,并且我必须加载所有成分,以便无论如何我都可以将它们传递给配方的构造函数?

这是大多数人所期望的 - 一个单一的存储库,可以加载包含在聚合边界内的所有状态。

我认为可能在 Recipe 聚合中包含聚合(食物)是不好的,但它真的很方便,因为它允许通过导航关系来计算食谱的营养价值。

  • 坏处大于好处。
  • 这在使用简化域来发现如何进行 DDD 时并不明显。

聚合的动机是限制模型中数据更改的方式,以便每次更改都使模型处于内部一致的状态。

如果您的域没有任何要强制执行的一致性约束(例如,当您的数据模型实际上只是一个数据库,并且数据模型对提议的更改没有否决权),那么问题空间不提供在选择良好的聚合边界方面有很多指导。

聚合背后的基本原则是您正在创建一种数据防火墙,您永远不必担心对 this 聚合的更改会违反 that的规则> 聚合。这意味着,除其他外,任何两个聚合都不应该共享可变数据。

共享值很好 - 每个聚合都有自己的值的不可变副本,并且更改一个聚合中的值根本不会影响另一个聚合。共享实体不好,因为实体是可变的。

拥有一个模型可以很方便地生成带有错误答案的查询,但其价值不如确保答案正确的模型。

通常情况下,一个聚合将通过 ID 引用另一个聚合。所以你的食谱看起来像

`Recipe`->*`Ingredient`->`Id<Food>`

Id&lt;Food&gt; 只是另一种不可变值类型;食谱可以很容易地改变它使用的食物,但它不能以任何方式改变食物。这反过来意味着您永远不必担心更改一个 Recipe 会破坏另一个。

【讨论】:

  • 从那时起我如何访问食品属性?我是否将它们全部复制到成分中?
  • 如果您的聚合边界绘制正确,则在更改食谱时无需修改食物。如果您正在响应查询,则您的实现是只读的,您无需担心更改任何内容,只需加载食谱,然后枚举 Id 以加载食物。
  • 对不起,我不明白你在这里的回答。我看到它的方式 当我编辑食谱或成分时,食物永远不会改变(即,如果我想更改食谱中的食物,我需要删除成分并添加新成分)。加载食谱然后加载食物是什么意思?食材如何从那里获取食物?
  • @Cedric 有几种方法可以解决这个问题。读取通常不通过域模型,因为域模型针对处理命令而不是促进读取进行了优化。例如,如果您想知道每份食谱有多少碳水化合物,这可以是在查询端进行的简单计算。如果计算非常复杂,并且您希望将其保留在模型中,那么最好将计算结果保存在聚合中。在这种情况下,您首先需要一种在域模型中执行它的方法。
  • 由于配方和成分是独立的集合,您必须使用一种机制,使您能够以最终一致的方式重新计算配方的营养成分。当食品营养成分发生变化(会发生这种情况吗?)时,可以发布 FoodNutritionFactsChanged 事件。然后,您将拥有一个订阅者,该订阅者将调用可以加载 Food 并调用 receipe.recalculateNutritionFacts(food) 的域服务。有几种可能的实现来实现这一点。不要忘记所有收据都必须在不同的交易中更新
猜你喜欢
  • 2021-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-22
  • 1970-01-01
  • 1970-01-01
  • 2014-08-11
相关资源
最近更新 更多