如何在 DDD 中正确设计模型的计算字段? [关闭]

Posted

技术标签:

【中文标题】如何在 DDD 中正确设计模型的计算字段? [关闭]【英文标题】:How to properly design model's calculated fields in DDD? [closed] 【发布时间】:2016-02-12 12:07:01 【问题描述】:

我正在成为DDD 的忠实粉丝。所以,我正在考虑将其正确应用到我当前开发的系统中。

假设我们有两个聚合根:OrderUserOrder 有两个属性,引用Userownercontractor。所有者创建了一个Order,承包商完成了它。

业主可以评价承包商履行Order 的质量。所以我们有一个Feedback 实体,连接到Order,包含评级。

现在,User 对象包含每个用户在他履行的订单中的平均评分。这部分让我有点困惑。

User 评分不仅仅是一个静态属性,实际上是Feedbacks 中所有评分的平均值。并且在Order 附加了Feedback 时,它已更改(需要重新计算)。

目前我有一些封装域逻辑的服务:OrderServiceUserService(我知道这实际上不符合 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 中正确设计模型的计算字段? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

DDD专栏4:DDD如何保护领域模型

在图像分类中如何计算正确的标签? [关闭]

什么是领域驱动设计 (DDD)? [关闭]

域驱动设计可以用例驱动吗?

DDD实践_如何使用DDD设计代码模型

DDD专栏7:DDD如何指导微服务设计实现