整洁的领域驱动设计 - George


这篇文章将介绍一种使用DDD和Clean Architecture构建应用程序的观点性方法。
我所说的 "观点 "是指,我将论证解决应用程序设计和架构中几个众所周知的问题的特定方式。当然,这并不意味着这是实现这些问题的唯一正确方法。
然而,它是DDD和架构领域长期以来的研究过程和成果。
网上有许多关于DDD核心概念的帖子和问题:一个问题问的是聚合和业务逻辑的位置,而业务逻辑执行的是跨越几个聚合的规则。
很多人似乎对一些事情很不清楚,比如准确的聚合边界、聚合根、实体之间的导航(在聚合内部)、聚合持久化(有无ORM)以及处理跨聚合关注和操作的逻辑放在哪里。这是可以理解的,这些确实是复杂的话题,非常依赖于问题领域的确切性质。
另一方面,在架构和设计领域也有一些有趣的发展,似乎是对DDD方法的补充。我在这里说的是Clean Architecture和CQRS等方法。
因此,考虑到这些,我想知道我是否能想出一个相对简单的方法,将这些方法中最优秀的想法结合起来,展示出一种建立强大的应用程序的方法,并且易于理解和维护。
 
案例
案例源码:GitHub repository.
它处理非常简单的域:有两个实体:Student和Course. 学生Student可以注册一门或多门课程Course。每门课程都会跟踪注册的学生人数,每个学生都会跟踪她注册的课程。显然,应用程序必须允许彼此独立地创建和编辑新的Course或新的。Student但是,为学生注册课程必须保证当且仅当学生成功注册了她的课程注册时,该课程的注册学生数量才会相应增加。
尽管这个领域非常简单,但它使我们能够关注几个重要问题。我们如何在聚合体和聚合根方面对这个领域进行建模?我们是否让其中一个实体(例如,课程是具有依赖实体 "学生 "的聚合根?或者反之亦然?或者我们需要另一个(根)聚合,比如说包含其他两个实体的 "入学"(Enrollment)?注册的逻辑在哪里,即:我们应该在哪里以及如何执行我们关于课程的注册学生数量的业务规则?
  
将重点从DDD转向Clean模式
我提倡的方法要求将焦点从纯 DDD 意义上的实体普遍转移到 Robert C. Martin 在他非常著名的文章中描述的用例。
Clean架构中的用例与DDD服务概念?

  • 用例UseCase可以将任何输出端口用于业务逻辑所需的任何操作。这在从持久层获取实体时特别有用。在某种程度上,Clean 范式中的用例UseCase是经典 DDD 意义上的应用程序服务和领域服务的组合。
  • 用例本质上是程序性的,但非常关注手头的业务需求。这增加了代码的可理解性。用例也以非常具体的业务场景命名。
  • 我们可以完全控制在每个特定用例中要执行的方法的确切顺序。
  • 用例级别的事务划分确保在发生错误或异常的情况下,聚合的状态将保持一致。
  • 遵循端口和适配器(清洁)架构的原则,用例非常容易测试。

 
DTO、值对象和不可变实体
领域驱动设计要求广泛使用诸如值对象和实体的战术模式。在我们的应用中当然也是如此。
此外,我认为,"查询 "类型的用例应该直接使用从持久层返回的DTOs,或者将这些DTOs映射到值对象。这是因为,这些对象是相关聚合体的几个属性的表示,注定要在UI层中用于非常具体的业务场景。
 
结论
总而言之,这篇文章提出了一种使用多种范式混合构建应用程序的特定方式。该方法在此处找到的示例中进行了说明。以下是要点:
  • DDD 用于对封装聚合内不变量(构造函数中的验证器)的域实体进行建模。
  • 用例(来自 Clean Architecture)用于以可理解和有针对性的方式编排聚合间的业务逻辑。
  • 用例级别的事务划分允许执行非聚合规则。
  • 从 CQRS 范式借用的方法允许从“命令”类型的用例中有效地将 ORM 用于 C(r)RUD 操作,而简单的 JDBC 查询(带有 SQL“JOIN”)用于需要访问的“查询”类型的用例到几个相关的聚合。

更多点击标题见原文。