【问题标题】:Real-World DDD: Structuring the Domain Layer真实世界的 DDD:构建领域层
【发布时间】:2011-04-06 20:35:51
【问题描述】:

我正在尝试从以数据为中心的设计和开发过渡到 DDD,并且已经阅读了 Evans 和 Nillson,但我仍然无法弄清楚我应该如何构建我的领域层。我确信我当前项目的性质没有帮助!

一点背景

该应用程序是管理人员评估的内部解决方案。人力资源人员将创建评估“模板”,其中包含一组问题,团队领导和经理要为他们的每个直接下属完成这些问题。答案将被保留以供审核和审查。这些评估可以针对各种各样的事情,例如对公司计划的反馈、绩效评估等。

我以数据为中心的一面

不影响解决方案,但突出我以数据为中心的思维方式,我已经对数据库架构有了一个愿景,将其包含在此处仅供参考(因为一张图片说明了一千个单词):

正如预期的那样,该架构已标准化,并且与我的应用程序中的数据处理方式不匹配。而且,我省略了查找表等,以尽量减少手头的问题。

用例

第一个用例是检索并显示用户需要完成的评估列表。这将在用户首次登录应用程序时显示,起初看起来相对容易,但有两个问题: 1 - 评估是基于时间的,因此可能需要每月、每年或每个“x”基于员工周年日的年数;并且,2 - 用户可以保存正在进行的评估并在以后完成。因此,该列表应包含到期的评估以及正在进行的评估。

接下来,当用户选择要执行的评估时,我需要检索该评估(当前版本)的所有问题,以便将它们显示给用户。在评估期间的任何时候,用户都可以保存当前结果。只有在完成整个评估之后,才能真正“提交”或提交。

第三,HR 需要一种方法来根据主管提供的回复重新生成评估。

最后,HR 能够创建和修改评估 - 并且它们是版本化的。因此,每当有人修改评估时,都会创建一个新版本,并且该版本将成为执行的任何新评估的模板(任何正在进行的评估继续使用其原始模板)。

领域模型

如果工作不正常,我将有一个作为聚合根的评估实体来满足第四个用例,这对我来说是有意义的。它将具有 Section 实体的子集合,而 Section 实体又将具有 Question 实体的子集合。它们都是实体,因为它们具有身份(是吗?)。评估是消费代码用于持久性、验证等的对象(尽管部分和问题实体验证自己并将状态汇总到根评估)。我的目标是从消费者那里抽象出版本控制并在数据持久层中实现它(好主意还是坏主意?)

这意味着我还将拥有一个为我处理持久性的 AssessmentRepository,并且可能还有一个在需要时创建新评估的 AssessmentFactory。

更大的问题来自其他用例。我也有 EmployeeAssessment 聚合根吗?还是只是一个实体?

查看用例,我需要通过几种方式使用这些信息。首先,当我生成要显示给用户的评估列表时,我不仅要根据评估频率评估直接报告列表,而且我还需要知道我是否已经开始和/或完成了评估对于那个员工。这来自 EmployeeAssessments 表。另一种情况是当用户实际执行评估时,我正在与 EmployeeAssessments 和 Responses 表进行交互。

从 UI 的角度来看,当用户执行评估时,他们对内部数据结构等一无所知。我需要向 UI 提供该评估的问题列表,以显示并接受响应列表坚持。这是否会导致带有随附存储库等的第二个根目录?

第三个用例类似,HR 希望能够在以后重新生成带有响应的评估。但是,我认为可以在此处使用执行评估时使用的相同流程,因为恢复现有评估需要相同的数据,唯一的区别是读/写能力与 HR 的只读能力。

已经结束了!

好的,我已经啰嗦了很多,我想我已经清除了玉米棒网的问题。我感谢任何方向、建议、批评等。正如我所说,我正在尝试跳跃并认为我理解这些概念,现在是应用它们的问题。谢谢!!!

【问题讨论】:

    标签: domain-driven-design


    【解决方案1】:

    几年前,我也和你一样跳跃。我现在正在从普通的 DDD 跳转到 CQRS(请参阅 cqrsinfo.com/)。

    我将采用 CQRS 方式,即使用事件存储并在架构级别将读取与写入完全分开。但是,我认为您所指的问题更符合普通的 DDD 方式 - 所以我将在这种情况下回答它。

    您需要完全摆脱“数据驱动”的思维。从主要工作流程开始。第一次和第三次使用本质上只是获取操作。我将首先关注聚合根状态发生变化的用例。因此,用例 2 即“执行评估”将是一个很好的起点。

    • 正如您正确指出的那样,聚合根将是评估。可以创建“PerformAssessmentService”类(相当于域服务),您的执行评估工作流程将存在于此。该工作流程将完全处于测试状态,其中所有依赖项(例如存储库)都被简单地剔除。

    • 您最终可能会在没有任何具体数据库/UI 实现等的情况下编写整个执行评估工作流。所有业务逻辑都在此域服务中编排,所有逻辑都存在于您的评估实体和其他关联实体中。

    • 进入下一个用例 - 可能是用例 4 - 修改评估(再次执行上述相同操作)

    • 将存储库/数据库、UI 等外围事物留到尽可能晚的实施中。首先锁定您域中的所有业务逻辑非常重要 - 然后将您的外围问题从域中移除 - 它更便宜/更高效(根据我的经验)

    请注意,没有正确的方法可以做到这一点,这只是我通常如何处理上述项目的概要。这里的关键是域实际上是所有实现的驱动力......

    【讨论】:

    • 好的,我和你在一起。让我们关注第一个用例,我需要为当前用户生成到期或正在进行的评估列表。我正在为解决评估是否包含在领域层中的逻辑与在 SQL 中可以轻松完成的逻辑而苦苦挣扎。我真的想让评估域对象包含一个评估版本对象列表,并让我的规范类创建一个使用“assessment.Versions”的表达式,以便所有逻辑都在代码中吗?
    • 另外,我认为我将拥有一个AssessmentService,它公开一个ListMyDueAssessments() 方法,该方法创建一个MyDueAssessmentSpecification 类的实例并将其传递给AssessmentRepository.Find(spec) 方法。 Find() 返回满足规范类提供的表达式的评估对象列表。我在正确的轨道上吗?返回一个更轻量级的扁平化版本的 AssessmentInfo 类怎么样,这是否与 DDD 一致?
    • 只是想在这个答案中添加一点,即通过使用 CQRS,您不需要 来实现事件溯源(即通过添加事件存储)。添加 ES 是可选的,可以在以后添加。 ES 存在时,具有其自身的复杂性,例如“最终一致性”。使用 CQRS,将写入与读取分开,结合存储层中的单个(甚至多个)关系数据库仍然是一个完全有效的选择。
    【解决方案2】:

    我会从一个使用内存存储库的快速而肮脏的原型开始。如果你走的是正确的道路,你会得到一个更好的主意。

    【讨论】:

    • 不幸的是,正是我正在做的事情导致了这些问题。你能解释一下你为什么选择存储库吗?
    猜你喜欢
    • 2022-10-17
    • 2011-04-03
    • 1970-01-01
    • 2012-05-11
    • 2013-03-07
    • 1970-01-01
    • 1970-01-01
    • 2019-07-19
    • 2011-06-03
    相关资源
    最近更新 更多