这篇文章是 软件架构编年史的一部分,这是一系列关于软件架构的文章。在其中,我写下了我在软件架构方面学到的知识、我对它的看法以及我如何使用这些知识。如果您阅读了本系列的前几篇文章,这篇文章的内容可能会更有意义。
领域驱动设计由埃里克·埃文斯 (Eric Evans) 在他 2003 年出版的精彩著作《领域驱动设计:解决软件核心的复杂性》(Domain-Driven Design: Tackling Complexity in the Heart of Software)中提出。埃里克· 埃文斯 (Eric Evans) 的书是将我们今天认为理所当然的许多软件开发概念形式化的关键。
我无法在博客文章中对 DDD 进行详尽的回顾。与 DDD 相关的重要概念太多了。幸运的是,这也不是这里的目标。然而,我要做的是列出 DDD 概念,我发现这些概念与我喜欢的代码组织方式以及我对架构的看法更相关:构成功能开发基础的系统范围的概念。
在这篇文章中,我将写下:
- 无处不在的语言
- 图层
- 限界上下文
- 反腐败层
- 共享内核
- 通用子域
无处不在的语言
软件开发中经常出现的问题,围绕着理解代码,它是什么,它做了什么,它是如何做的,为什么要这样做......如果它使用的术语与术语不同,那么理解代码会更加复杂领域专家使用,例如,如果领域专家谈论老年用户而代码谈论监管者,这可能会在讨论应用程序时带来很多混乱。然而,大多数这种歧义都可以通过正确命名类和方法来解决,使它们表达对象是什么以及方法在域上下文中的作用。
使用通用语言的主要思想是使应用程序与业务保持一致。这是通过在代码中采用业务和技术之间的通用语言来实现的。语言的来源是公司的业务方,他们有需要实施的概念,但是术语是与公司的技术方协商的(这意味着业务方并不总是选择最好的命名either) 的目标是创建一个通用的术语,可以被业务、技术和代码本身使用而没有任何歧义,一种无处不在的语言。代码、类、方法、属性和模块命名必须与通用语言保持一致。如果需要,值得重构代码!
图层
我已经在之前的帖子中讨论过分层,但我发现此时重要的是要记住 DDD 标识的层:
- 用户界面
- 负责绘制用户用来与应用程序交互的屏幕,并将用户的输入转换为应用程序命令。需要注意的是,“用户”可以是人类,也可以是连接到我们 API 的其他应用程序,这完全对应于EBI 架构中的 Boundary 对象 ;
- 应用层
- 编排域对象以执行用户所需的任务:用例。它不包含业务逻辑。这与EBI 架构中的交互器有关,除了交互器是与 UI 或实体无关的任何对象,在这种情况下,应用程序层仅包含与用例相关的对象。这一层是应用服务所属的层,因为它们是用例编排发生的容器,使用存储库、域服务、实体、值对象或任何其他域对象;
- 领域层
- 这是包含所有业务逻辑、领域服务、实体、事件和任何其他包含业务逻辑的对象类型的层。显然和EBI的Entity对象类型有关。这是系统的核心。领域服务将包含不太适合实体的领域逻辑,通常会协调多个实体以完成某些领域操作;
- 基础设施
- 支持以上层的技术能力,即。持久性或消息传递。
埃里克·埃文斯,2003 年
限界上下文
在企业应用程序中,模型可以增长很多,并且在代码库上工作的团队规模也会增长。这给我们带来了两个问题:
- 开发人员必须处理的代码库越大,认知负荷越大,理解代码的难度就越大,因此引入错误和判断错误的可能性就越大;
- 在同一个代码库上工作的开发人员越多,协调工作和对应用程序有共同的技术和领域愿景就越困难。
换句话说,手头的问题变得太大了。
大问题的通常解决方案是将其分解成更小的部分,而这正是“限界上下文”发挥作用的地方。
两个子系统通常服务于截然不同的用户群体
Eric Evans 2014,领域驱动设计参考
有界上下文定义模型的隔离部分应用的上下文。隔离可以通过解耦技术逻辑、代码库隔离、数据库模式隔离以及团队组织来实现。像往常一样,我们隔离限界上下文的程度取决于实际情况:我们拥有的需求和可能性。
有趣的是,这并不是一个全新的概念。早在 1992 年,Ivar Jacobson 在他的书中就写了关于子系统的文章,比 Eric Evans 早 11 年!
伊瓦尔·雅各布森,1992 年
那时他已经对这个主题有了很多非常具体的想法:
- 因此,该系统由许多子系统组成,这些子系统可以包含它们自己的子系统。在这种层次结构的底部是分析对象。因此,子系统是为进一步开发和维护构建系统的一种方式
- 子系统的任务是打包对象以降低复杂性。
- 与功能的特定部分有关的所有对象都将放在同一个子系统中
- 目的是在子系统内部实现强功能耦合,子系统之间实现弱耦合(现在称为低耦合高内聚)
- [一个子系统]因此最好只耦合到一个参与者,因为变化通常是由参与者引起的
- […] 首先将控制对象放在一个子系统中,然后将强耦合的实体对象和接口对象放在同一个子系统中
- 所有具有强相互功能耦合的对象将被放置在同一个子系统中 [...]一个对象的改变会导致另一个对象的改变吗?(这现在被称为共同闭包原则——一起改变的类被打包在一起——由 Robert C. Martin 于 1996 年在他的论文“粒度”中发表,比 Ivar Jacobson 的书晚了 4 年)他们与同一个演员交流吗?它们是否都依赖于第三方对象,例如接口对象或实体对象?一个对象是否对另一个对象执行多个操作?(这现在被称为共同重用原则——一起使用的类被打包在一起——由 Robert C. Martin 在 1996 年的论文“粒度”中提出,比 Ivar Jacobson 的书晚了 4 年)
- 划分的另一个标准是不同子系统之间的通信越少越好 (低耦合)
- 对于大型项目,子系统划分可能有其他标准,例如:不同的开发组具有不同的能力或资源,因此可能需要相应地分配开发工作(这些组也可以在地理上分开)在分布式环境中,每个逻辑节点(SOA、Web 服务和微服务)可能需要一个子系统如果一个现有的产品可以在这个系统中使用,这可能被认为是一个子系统(我们系统依赖的库,即ORM)
反腐败层
反腐败层基本上是两个子系统之间的中间件。它用于隔离两个子系统,使它们依赖于反腐败层,而不是直接相互依赖。这样,如果我们重构或完全替换其中一个子系统,我们只需更新反腐败层,而另一个子系统保持不变。
当我们有一个需要与遗留系统集成的新系统时,这尤其有用。为了不让遗留结构决定我们如何设计新系统,我们创建了一个反腐败层,使遗留子系统的 API 适应新子系统的需求。
它有3个主要关注点:
- 使子系统 API 适应客户端子系统的需求;
- 在子系统之间转换数据和命令;
- 根据需要在一个或多个方向上建立通信
埃里克·埃文斯,2003 年
当我们不控制一个或所有子系统时使用这种技术更合乎逻辑,但当我们控制所有相关子系统时使用它也可能有意义,即使它们设计良好但简单有非常不同的模型,我们希望防止从一个模型泄漏到另一个模型(更改一个子系统以匹配另一个子系统的需求)。
共享内核
在某些情况下,尽管我们希望拥有完全隔离和解耦的组件,但某些域代码由多个组件共享是有意义的。
这将允许组件保持彼此分离,尽管耦合到相同的共享代码,共享内核。
例如,由一个组件触发并由另一个或多个组件侦听的事件就是这种情况。但服务接口甚至实体也可能是这种情况。
尽管如此,我们应该保持共享内核较小,并且在更改它时要非常小心,以免无意中破坏使用它的其他代码。重要的是,在未与使用它的其他开发团队协商的情况下,不得更改共享内核中的代码。
通用子域
子域是域中隔离得很好的部分。通用子域是不特定于我们的应用程序的子域,它可以用于任何类似的应用程序。
所以,如果我们有一个应用程序,其中有一部分是关于金融的,也许我们可以在我们的应用程序中使用现有的金融库。但无论哪种方式,即使我们不能使用现有的库而需要构建我们自己的库,如果它是一个通用的子域,它也不是我们的核心业务,它应该被认为是必不可少的,但不是至关重要的。它不是我们应用程序中最重要的部分,所以它不是我们最好的专家应该关注的地方,它甚至应该明显在主要源代码之外,可能与依赖管理工具一起安装。
结论
我选择在这里采用的 DDD 概念主要是关于单一职责、低耦合、高内聚、隔离逻辑,以便我们的应用程序变得更加一致、更容易和更快地改变和适应业务需求。
参考资料:https://herbertograca.com/2017/09/07/domain-driven-design/