如何在 DDD 中正确设计模型的计算字段? [关闭]
Posted
技术标签:
【中文标题】如何在 DDD 中正确设计模型的计算字段? [关闭]【英文标题】:How to properly design model's calculated fields in DDD? [closed] 【发布时间】:2016-02-12 12:07:01 【问题描述】:我正在成为DDD 的忠实粉丝。所以,我正在考虑将其正确应用到我当前开发的系统中。
假设我们有两个聚合根:Order
和 User
。 Order
有两个属性,引用User
:owner
和contractor
。所有者创建了一个Order
,承包商完成了它。
业主可以评价承包商履行Order
的质量。所以我们有一个Feedback
实体,连接到Order
,包含评级。
现在,User
对象包含每个用户在他履行的订单中的平均评分。这部分让我有点困惑。
User
评分不仅仅是一个静态属性,实际上是Feedbacks
中所有评分的平均值。并且在Order
附加了Feedback
时,它已更改(需要重新计算)。
目前我有一些封装域逻辑的服务:OrderService
和UserService
(我知道这实际上不符合 DDD,但我稍后会重构)。当 OrderService 接收到将反馈附加到订单的命令时,它会发出 OrderFeedbackAttachedEvent
UserService 监听以重新计算用户评分。
令我不满意的是,关于 Order 聚合根的知识现在泄露到了 UserService 中。而且我看不到逃脱它的方法。我开始认为应该有一些模式来处理这种情况。
评分似乎是用户的完美属性。但它不是一个静态的、持久的值,而是基于其他对象数据计算的某种东西,这一事实让我怀疑。
评级本身也不是一个实体。它也不是一个值对象。我想知道,在 DDD 中它是什么?以及如何在不牺牲性能和易用性的情况下对系统中的评分(或任何其他可计算值)进行建模)?
【问题讨论】:
@plalx 我担心的是,在给出反馈后,必须汇总所有反馈来计算用户评分。这会创建从用户到订单的依赖关系(当事件被捕获时,调用订单存储库以从用户服务获取评级)。这样可以吗,你怎么看? 【参考方案1】:看来您可能至少有 2 个独立的有界上下文:一个用于排序,另一个用于反馈。
了解有界上下文可以让您看到同一物理事物的不同抽象:在“订单”上下文中,订单似乎是一个合法的聚合,但它可能是“反馈”BC 中的一个值对象,它会持有一个订单 id(该值来自订单 BC 通过事件)。
这是一个建议:
使用此模型,您将在承包商聚合的事件处理程序中从反馈聚合处理“FeedbackEmitted”事件时计算承包商的平均评分
希望这会有所帮助:)
【讨论】:
您用什么来创建图表? @DavidOsborne : 我使用了plantuml.com/plantuml 语法记录在plantuml.com @Mehdi。感谢这个神奇的工具!【参考方案2】:公平地说,从另一个 AR 或另一个 BC 发出的事件不是泄漏。在我看来,User
AR 处理OrderFeedbackGiven
事件没有问题。如果Feedback
VO 是事件的一部分,那么客户端不需要依赖其他任何东西来处理事件。拥有一个有界反馈的上下文可能会更干净,但我不会为此实现一个完整的 BC,否则你将会有大量的微型 BC...
我担心的是,在给出反馈后,所有 必须汇总反馈以计算用户评分。这创造了一个 从用户到订单的依赖关系(调用订单存储库来获取 用户服务的评级,当事件被捕获时)。这样可以吗,怎么办 你觉得呢?
我认为如果你这样做也没关系。应用服务层作为一个整体依赖于领域层。但是,根本不需要这样做,因为您可以计算 moving cumulative average。
例如其中this.ordersFeedbackAvg
是一个MovingAvg
值对象,用于跟踪平均值以及计算该平均值的数据点数。
when(OrderFeedbackGiven feedback)
this.ordersFeedbackAvg = this.ordersFeedbackAvg.cumulate(feedback.mark);
【讨论】:
感谢您的好主意! @VladislavRastrusny 我的示例不完整,因为它没有对单个订单的平均值进行建模,而且我没有过多关注语言,但你明白了。 @VladislavRastrusny 我在回答中没有提到的另一个方面是并发性。你真的需要User
实体来保存反馈平均值吗?现在涉及User
AR 的任何其他命令可能同时与处理OrderFeedbackGiven
事件发生冲突。由于这个移动平均线最终是一致的,因此您可以创建另一个仅对此负责的 AR,这将减少对 User
AR 的争用。为什么平均值是User
AR 的一部分?您是否在此 AR 处理的任何命令中使用了该信息?
不要忘记不必针对域模型进行查询,因此可以从多个 AR 聚合数据以获取单个视图。您的事件处理是单线程的还是可以一次处理多个事件?使用最终一致性时的另一种策略是一次接受许多请求,但以单线程方式处理它们。因此,如果您有一个专门用于平均反馈的 AR,您甚至不需要使用这种方法进行锁定。
@VladislavRastrusny 太棒了!那么我认为我可以给你的最好的建议是,当你设计你的对象模型时,你应该完全忘记查询。域模型旨在通过创建作为事务边界的 AR 来完成命令并保护不变量。另一方面,查询通常会跨越这些边界,因此我认为最好不要查询域模型并直接进入数据库。然而,没有必要拥有成熟的 CQRS(不同的 DB + 异步投影)......以上是关于如何在 DDD 中正确设计模型的计算字段? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章