经典分层

分层是打破复杂软件系统的最广为人知的技术之一。它已被推广到许多流行的书籍,如马丁·福勒的企业应用架构模式

图层允许我们在不知道任何较低级别层的详细信息的情况下在较低级别层之上构建软件。在理想世界中,我们甚至可以用不同的实现替换低层。虽然层数可能有所不同,但我们在实践中大多看到三到四层。

在这里,我们有一个三层架构的示例图:

img

演示层包含与用户(或 API)接口相关的组件。在层中,我们发现与应用程序解决的问题相关的逻辑。数据库访问层负责数据库交互。

依赖方向从上到下。演示文稿层中的代码取决于域层中的代码,而层本身确实依赖于数据库层中的代码。

例如,我们将检查一个简单的用例:创建一个新的用户。让我们将相关类添加到层图中:

img

数据库层中,我们有一个用户道类,其中包含一个可接受用户位类的保存用户 (.) 方法。用户本地可能包含用户道与数据库交互所需的方法。使用 ORM 框架(如JPA),用户位可能包含与对象关系映射相关的信息。

域层提供用户服务和用户类。两者都可能包含域逻辑。用户服务与用户道交互,以保存数据库中的用户。用户道不知道用户对象,因此用户服务需要在调用用户道之前将用户转换为用户。

在演示文稿层中,我们有一个用户控制器类,该类使用用户服务和用户类与域层进行交互。演示文稿也有其自己的类来表示用户:用户Dto 可能包含用于在用户界面中格式化演示文稿的字段值的实用方法。

这有什么问题?

我们有一些潜在的问题要在这里讨论。

首先,我们可以很容易地得到的印象是,数据库是系统中最重要的部分,因为所有其他层都依赖于它。然而,在现代软件开发中,我们不再从为数据库层创建巨大的 ER 图开始。相反,我们通常(应该)专注于业务领域。

由于域层依赖于数据库层,域层需要将其自己的对象(用户)转换为对象,数据库层知道如何使用(用户身份)。因此,我们有处理位于域层中的数据库层特定类的代码。理想情况下,我们希望有域层专注于域逻辑,而没有别的。

域层直接使用数据库层中的实现类。这使得很难用不同的实现来替换数据库层。即使我们不想计划用不同的存储技术替换数据库,这一点也很重要。考虑用模拟来替换数据库层,用于单元测试或使用内存数据库进行本地开发。

带界面的抽象

最新的提及问题可以通过引入接口来解决。显而易见且非常常见的解决方案是在数据库层中添加一个界面。高级层使用界面,不依赖于实施类。

img

在这里,我们将用户道类拆分为界面(用户道)和实现类(用户道动)。用户服务仅使用用户道界面。这种抽象化为我们提供了更大的灵活性,因为我们现在可以更改数据库层中的用户道实现。

但是,从层的角度看,一切都没有改变。我们的域层中仍有与数据库层相关的代码。

现在,我们可以通过将界面移动到域层来施展一点魔法:

img

请注意,我们不只是移动用户道界面。由于用户道现在是域层的一部分,它使用域类(用户)而不是数据库相关类(用户位)。

这一小变化正在扭转域和数据库层之间的依赖方向。域层不再依赖于数据库层。相反,数据库层依赖于域层,因为它需要访问用户道界面和用户类。数据库层现在负责用户和用户之间的转换。

仔细地

当依赖方向已更改时,调用方向保持不变:

img

域层是应用程序的中心。我们可以说,演示层调用域层,而域层调用到数据库层。

作为下一步,我们可以将层拆分为更具体的组件。例如:

img

这就是六边形架构(也称为端口和适配器)的意义所在。

我们这里不再有了。相反,我们在中心有应用程序域和所谓的适配器。适配器提供额外的功能,如用户界面或数据库访问。一些适配器调用域中心(这里*:UIRESE API),而另一些适器则通过接口(这里数据库*、消息队列电子邮件)被域中心调用)

这允许我们将单独的功能部分放入不同的模块/包中,而域逻辑则没有任何外部依赖性。

洋葱结构

从上一步开始,很容易移动到洋葱结构(有时也称为清洁架构)。

img

领域中心被分割成域模型域服务(有时称为使用案例)。应用程序服务包含进进出出适配器。在最外层,我们定位数据库或消息队列等基础设施元素。

记住什么?

我们研究了从经典分层建筑到更现代建筑方法的过渡。虽然六边形架构和洋葱结构的细节可能有所不同,但两者都具有重要部分:

  • 应用程序域是应用程序的核心部分,没有任何外部依赖。这允许轻松测试和修改域逻辑。
  • 域逻辑周围的适配器与外部系统对话。这些适配器可以轻松地被不同的实现所取代,而无需对域逻辑进行任何更改。
  • 依赖性方向始终从外部(适配器、外部依赖)到内部(域逻辑)。
  • 调用方向可以进出域中心。 至少对于调域中心,我们需要接口来确保正确的依赖方向。