4+2 分层架构 - Ricardo

22-09-13 banq

应用于软件架构的关注点分离,这个4+2 分层架构提案是对clean Architecture的改进,仅公开了领域业务规则最相关的概念以及接口 API与注入实现的使用。

介绍
许多原则推动了软件开发,而关注点分离可能是最基本的,许多其他原则都源自于它。函数派生自它。类、对象和方法都派生自它。模块化和分层架构源自它。
在类的微观维度中,关注点分离意味着一些“最佳实践”,例如:不要在同一方法中混合抽象级别;不要在同一个方法和类中混合职责。
更进一步,关注点分离推动了模块化,告诉我们将业务规则和领域模型与数据访问和用户界面分开。告诉我们在库中分离相关和可重用的代码。
关注点分离也是我们的目标,即使用允许模块可插入和可测试的接口/实现策略来管理低耦合的依赖关系。中央模块接口定义API,而提供者模块实现这些API,使两个模块都可独立测试。
在本文中,我将重新审视分层架构,并提出一个精致的视图,只展示最重要的概念。

分层架构和依赖注入
当应用于软件架构时,这一原则是许多分层架构的基础,这些架构从模型视图控制器 (MVC) 演变为模型视图展示器 (MVP)、六边形架构、洋葱架构⁴和清洁架构⁵等。
最初的Clean Architecture提案由 Robert Martin (Uncle Bob) ⁵在 2012 年提出,它使用 Ivar Jacobson ²、Steve Freeman 等人的软件工程概念描绘了层。Bob Martin 指出,所有这些先前的架构都有相同的目标:关注点分离以产生以下系统:

  • 独立于框架
  • 可测试
  • 独立于 UI
  • 独立于数据库
  • 独立于外部机构



上图罗伯特马丁论文的原始图像

但是在这些架构提案中存在一个概念上的挑战,即内层对外层“一无所知” 。这实际上是正确分层的静态依赖要求:层应该只依赖于内层类型。这是“必须遵守”的规则!
另一方面,在运行时内层确实依赖于外层提供的实现。例如,域用例确实依赖于来自外部持久层的数据存储库实现。
这个挑战通过依赖倒置原则或更具体地通过依赖注入机制“解决” ,其中内部类被注入其依赖的外部实现。更笼统地说,我们可以说:“在运行时,内部层配置有众所周知的接口的未知实现”。
1978 年,Trygve Reenskaug 在 Xerox PARC ¹为 Smalltalk 设计 MVC 架构时,首先使用了这种使用注入实现的控制反转机制。然后它被广泛用于 Smalltalk-80 语言和环境。
唐纳德·华莱士 (Donald Wallace) 在 1980 年也在施乐 PARC 为 Mesa 项目开发组件时,称其为好莱坞定律的应用——“不要打电话给我们,我们会打电话给你”。
大约在 1994 年,Bob 叔叔使用了Dependency Inversion Principle的表达方式,并在 2004 年 Martin Fowler 创造了Dependency Injection 一词。

重新审视清洁Clean架构
这种分层概念随着技术和经验的发展而成熟,总是朝着组件之间的更少耦合方向发展,有效地变得更加可测试、灵活和适应性强。
我想重新审视清洁架构并提出一个分层视图,它揭示了我称之为4+2 分层架构中最重要的概念,它包含:
4个主要层次:

  • 领域层(业务规则)
  • 数据层(持久性)
  • 服务层(外部服务)
  • 表示层(UI)

2 个附件层:
  • 核心层(共享库)
  • 依赖注入层(依赖倒置)



上图4+2分层架构

这与以前的分层架构并没有背离,而是一次回顾,一方面提出了更通用的层定义(未提及 Web、控制器和其他框架或技术解决方案),另一方面明确定义了核心领域概念,即接口/实现的使用和依赖注入的必要性。

让我们详细看看这些层:

领域层
包含独立于基础设施和表示的核心概念和规则。它表示为应用程序建模的状态和行为。它包含以下概念:

  • 实体
  • 业务逻辑(用例、验证器、异常)
  • 数据存储库接口
  • 服务接口

该层不静态依赖于外部定义(类型),但在运行时它可能依赖于满足其内部接口定义的注入实现(依赖倒置原则)。
这种低耦合结构使该中央层无法抵御外部变化,从而创建了一个高度可测试且独立的组件。

数据层
包含持久性基础设施实现和适配器。它可能包含:

  • 数据模型
  • 模型/实体映射器
  • 数据存储库实现(持久性)

再一次,关注点分离提出了一个可测试和可插拔的模块,可以满足内部领域层接口的要求。
有时这一层可能会有替代实现,例如支持不同的数据库。每个实现都可以独立测试。所选择的替代方案将由依赖注入机制选择。
替代实现并不总是那么简单,因为特定的解决方案可能会影响内层 API 中的定义。这违背了内层独立性的提议,但我们可能不得不处理它。例如,一些持久性框架使用异步调用,而另一些是同步的,这样的改变很可能需要在整个应用程序中重新定义。但是由于我们组织得很好,我们的影响点也很明确,通常在接口和用例级别。

服务层
这是一个可选层,用于对数据持久性之外的外部依赖项进行建模,例如对 Web 服务、远程设备、硬件等的访问。
当应用程序不依赖外部服务时,该层将被省略,成为3+2 分层架构变体。

表示层
这是呈现信息并与用户交互的层。它仅取决于内层定义的类型,主要取决于Domain Use Cases、Entities和Exceptions。正是接口和实现注入的“魔力”使得这一层可以在运行时使用数据和服务层提供的代码。
这个层的内部组织非常依赖于框架,这里的主要概念是没有其他层依赖于这一层,并且可以实现多个表示替代方案并独立运行。例如,同一个应用程序可能会提供一个 Web UI、一个移动 UI 和一个桌面 UI,它们都使用相同的域用例。

核心层
这个附属层也是可选的,它是一个模块,用于组织不属于域层但必须广泛用于应用程序的公共接口和实用程序类和函数。

依赖注入层
这是最外层;它将包含依赖注入机制。根据语言和库的不同,这可能只是配置文件(XML、JSON 等)。在其他平台上,该层可能包含 DI 代码以编程方式配置内部层。
作为最外层,它将具有查看所有应用程序代码的不良能力。一些语言确实提供了控制内部层可见性的方法,允许每个层只公开所需的API和实现(原则“让正确的事情变得容易,让错误的事情变得困难”)。

层间关系
我们已经提到内层不应该依赖于外层的类型,并且在运行时内层可以配置有众所周知的接口的外部实现。
在这里,关注点分离提醒我们,我们应该使用促进层独立性的机制来最小化一层相对于其他层的知识。Gilad Bracha 的新话语言³使用嵌套类,因此嵌套了没有全局范围的命名空间,是模块化和依赖项提供的漂亮实现。
在基于类或对象的语言中,通过为每个层实现层管理器对象来应用关注点分离会很有趣,该对象将显式公开其层依赖关系并在注入所需实现时配置其模块。我还为这个4+2 分层架构的Flutter实现制作了一个补充文档和视频教程,它将这个原理与 AppLayer 对象应用于层间交互;结构化实现可作为公共 github 模板项目⁶

更大的应用
在大型领域中,我们的4+2 分层架构命题将解决领域宇宙的特定背景。关注点分离表明应该将大型应用程序划分为有凝聚力的互连模块。然后将使用4+2 分层架构方法单独开发每个模块。

测试
测试是基础,并已成为许多软件开发实践(敏捷、测试驱动、持续集成)的标准。应用程序架构是允许和促进有效可测试性的关键组件。
人们普遍希望在我们的测试中获得最大的代码覆盖率,但这通常是一把双刃剑——大量的测试会导致重构代码的大量工作;大量的测试通常会驱动大量的复制/粘贴开发(无意识测试)。
我们首先需要对用户故事进行良好的测试覆盖,探索业务规则和边界案例,通常针对模拟实现或配置的依赖项(众所周知的行为)。
其次,我们需要测试实现 API(黑盒测试)。再次,使用边界情况和众所周知的依赖关系。
这种分层架构确实通过强制大多数层间交互确实依赖于用例(业务规则)和实现 API来提高可测试性。

结论
分层架构是将关注点分离应用于应用程序结构的结果。这个4+2 分层架构提案是对Clean Architecture的改进,仅公开了领域业务规则最相关的概念以及接口 API与注入实现的使用。

案例源码
 

1