将业务规则与域驱动设计中的实体分开

时间:2018-01-21 09:20:10

标签: domain-driven-design software-design

当我在我的软件项目中练习DDD时,我一直面临的问题是"为什么我要在实体中实施我的业务规则?他们不应该是纯粹的数据模型吗?"

请注意,根据我对DDD的理解,域模型可以包含持久模型和值对象。

我想出了一个解决方案,我将持久模型与我的域模型分开。另一方面,我们有数据传输对象(DTO),因此我们有3层数据映射。数据库到持久性模型,持久性模型到域模型和域模型到DTO。在我看来,我的解决方案不是一个有效的解决方案,因为必须付出太多的努力。

因此,有没有更好的实践来实现这一目标?

3 个答案:

答案 0 :(得分:3)

免责声明:这个问题的答案要大一些,但需要了解问题;也是100%根据我的经验。

你的感觉是正常的,我前段时间有同感。这是因为体系结构,编程语言和使用的框架的组合。您应该尝试选择上述工具,以便提供最容易更改的代码。如果您必须为添加到实体的每个字段更改3个类,那么这将是大型项目中的噩梦(即50多个实体类型)。

问题是每个实体/概念都有多个DTO。

我使用的最重的架构是Classic分层架构;严格的版本是最难的(在严格的版本中,图层可能只访问它之前的图层;即用户界面可能只访问应用程序)。当数据从基础架构移动到UI时,它涉及许多DTO和翻译。测试也很难,因为我不得不使用大量的嘲弄。

Classic layeredarchitecture

然后我颠倒了依赖关系,Domain将不依赖于Infrastructure。为此,我在Domain层中定义了在Infrastructure中实现的接口。但我仍然需要使用嘲弄他们。此外,聚合体不是纯粹的,它们有副作用(因为它们称为基础设施,即使它被接口抽象)。

然后我将域名移到最底层。这使我的聚合物变得纯净。我不再需要使用嘲弄。但是我仍然需要DTO(由Application层返回到UI和ORM使用的那些)。

enter image description here

然后我做了第一次飞跃:CQRS。这将模型分为两部分:写模型和读模型。重要的是,您不再需要将DTO用于模型。 Aggregate(写入模型)可以按原样序列化或转换为JSON并存储在几乎任何数据库中。 Vaughn Vernon有一个blog post about this

enter image description here

但最好的是Read型号。您可以为每个用例创建一个读取模型。作为仅用于读取/查询的模型,它可以尽可能简单/转储。读取实体仅包含与查询相关的行为。通过正确的持久性,它们可以保持原样。例如,如果您使用MongoDB(或任何文档数据库),使用基于反射的简单序列化程序,您可以拥有非常精简的体系结构。由于域事件,您不需要使用JOINS,您可以进行完全数据非规范化(读取实体包括他们需要的所有数据)。

第二个飞跃是事件采购。有了这个,你不需要聚合的平坦持久性。每次处理命令时,它们都会从事件存储中重新水化。

您仍然拥有DTO(命令,事件,阅读模型),但每个实体/概念只有一个DTO。

关于消除演示文稿使用的DTO:您可以使用类似GraphSQL的内容。

编程语言和框架可以使上述所有内容变得更糟。强类型编程语言强制您为每个自定义返回值创建一个类型。一些框架强制您返回自定义可序列化类型,以便通过HTTP请求将它们返回到REST(这样您就可以使用反射自行描述REST端点)。在PHP中,您可以简单地使用带有字符串键的数组作为REST控制器返回的值。

P.S。

  • 通过DTO我的意思是一个有数据且没有行为的类。
  • 我不是说我们都应该使用CQRS,只是你应该知道它存在。

答案 1 :(得分:2)

  

为什么我要在实体中实施业务规则?他们不应该是纯粹的数据模型吗?

您的持久性实体应该是纯数据模型。您的实体描述了行为。他们不是一回事;在存储库中有一些逻辑可以将一个逻辑更改为另一个。

我知道管理事物的最简单方法是将持久性实体视为由域实体管理的值对象,并使用data mapper之类的内容进行转换在域和持久性之间。

  

另一方面,我们有数据传输对象(DTO),因此我们有3层数据映射。数据库到持久性模型,持久性模型到域模型和域模型到DTO。在我看来,我的解决方案不是一个有效的解决方案,因为必须付出太多的努力。

在此提供了一些简化,基于这样的想法:如果您要实施查询,则不需要"域模型"因为你实际上并没有改变支持数据。在这种情况下,您可以采用"域模型"完全脱离了循环。

答案 2 :(得分:2)

DDD和数据是截然不同的事情。汇总数据(结果)将以某种方式保留,具体取决于您使用的内容。我个人认为在域事件中所产生的域事件是DTO(技术上是),可以直接存储在事件存储中(如果您使用事件源)或充当持久性模型的数据源。

域模型表示相关的域行为,域状态为'结果'。与仅表示业务语义值的值对象相比,实体是具有id的概念。实体通常将相关的值对象和一致性规则分组。 Not all business rules are here , some of them make sense as a service.

现在,有一个CRUD域或CRUD建模的情况,基本上你所拥有的是一些数据结构和一些验证规则。如果建模是正确的,不需要在这里使您的生活复杂化。尽可能简单地实现。

始终将DDD视为收集需求和构建信息的方法。代码(设计)中的实现是不同的。