目录
Clean分离
分层架构
当层变成千层面
驱动和被驱动
六边形架构/端口和适配器
应用和领域
通往外部世界的端口
使用适配器插入
结论
同样,在任何重要的软件项目中,一半的战斗都是管理复杂性。事实上,您可能会争辩说,任何软件专业人员的主要作用是驯服复杂性,以使我们工作的系统易于更改。
划分应用程序的功能区域是使其易于管理的关键。我们不想将持久性框架的关注点与核心业务逻辑、用户界面或代码中发生的任何其他事情混淆。根据单一职责原则,我们希望将因相同原因而发生变化的事物聚集在一起,并将因不同原因而发生变化的事物分开。这样做可以简化代码的推理、测试和简单的旧维护。
将因相同原因而发生变化的事物聚集在一起。将那些因不同原因而改变的东西分开——单一职责原则
这些层捕获应用程序的相关部分。出于类似原因而更改的部分保持在一起(内聚),并与程序中关联度较低的部分分离(解耦)。任何层都应该只依赖于它自己的一部分或它下面的层。这使我们能够在应用程序的一个领域工作,而不必担心其他问题。
这是一种易于理解且广泛使用的模式,对于许多软件来说非常有用。然而,它并非没有问题。
尽管分层架构有明确的分离,但边界并不总是得到很好的维护。
无论是否有意识,开发人员偶尔会模糊架构的线条以将功能推出门外。例如,使用数据库存储过程来执行业务级任务会将关注点分散到多个层。当您陷入困境时,这可能并不明显,因此几个月后,这些层开始相互渗透,混合在一起,破坏了架构的许多好处。
此外,分层方法暗示了架构的单一维度。如果您需要的不仅仅是用户界面来驱动应用程序——添加 CLI、REST API 或事件流作为输入怎么办?当然,您可以将它们添加到分层架构中,但它们不太适合一维心智模型。
让我们翻转一个分层架构。
用户界面驱动应用程序。它是所有交互的源泉。软件的核心(领域及其应用程序服务)对驱动端的交互做出反应,并依赖于较低层来帮助它满足这些请求。我们可以说那些较低(最右边)的层是被驱动的。
这里存在一个有趣的对称性。驱动端和被驱动端都是与我们应用程序之外的事物的交互。它们本身并不提供核心价值——这是领域的工作——而是帮助我们将核心价值传递给世界。
软件提供的价值保存在这些中间层中,我们应该使与它们的接口——包括测试——尽可能干净。同样,我们希望防止外部担忧侵犯我们软件的核心目的。
既然我们已经断言我们的应用程序的中心是最有价值的部分,我们可以改变我们的架构来保护它。
我们要做的关键改变是让依赖项向内——在域——而不是像以前一样指向链中的下一层。这样做可以让我们根据需要注入细节(数据库、用户界面等),并使我们的核心抽象保持干净和不受影响。
抽象不应该依赖于细节。细节(具体实现)应该依赖于抽象——依赖倒置原则
我们从领域模型开始。我们将其包装在应用程序服务(与我们的应用程序层相同)中,作为我们软件的核心。
这个捆绑包通常与技术无关——它只涉及我们的软件旨在解决的问题。这使得推理、与领域专家讨论和测试变得更加简单。随着我们的发展,我们不再在头脑中处理正交问题。
当然,我们需要在这个核心中插入一些东西,否则它完全没用。这就是端口的用武之地。
端口只是一个接口。在驾驶方面,这个界面是调用我们的软件,询问它的问题——例如,将 10 美元存入我的银行账户。在驱动端,我们有接口将被域调用以使用外部资源——数据库、外部 API 等。
六边形架构的“六边形”部分是为了证明一个应用程序可能有许多不同的东西插入这些端口。我们可以使用 GUI 和测试工具驱动软件,或者在后端插入不同的数据库技术。
端口只是接口,所以我们需要一些东西来实现它们。我们要与之交互的系统不会兼容,因此我们需要使用适配器模式从一个域转换到另一个域。
每个端口都可以通过适配器安装,以允许外部系统与我们的应用程序对话,或者(在右侧)让我们的应用程序与外部系统对话。
如前所述,这些适配器被注入,以便界面或数据表为中心的抽象不依赖于它之外的细节。
这是六边形结构的最终形式。我们已经从以层为幌子的善意但最终耦合的架构转变为更清洁、更健壮的架构。
端口和适配器使我们能够在系统中保持牢固的边界,将不相关的事物很好地划分。我们在六边形周围有多个端口这一事实允许我们在每个端口插入多个系统,并通过测试适配器确保我们不会意外地将内核与外层耦合(通过三角测量)。
当然,一切都是有代价的。需要定义端口接口,并且需要开发适配器以在外层和内层的语义之间进行转换。这并非完全微不足道,并且确实带有一些维护开销/样板。
对于简单的软件,六边形架构几乎可以肯定是矫枉过正。不过,对于更复杂的应用程序——尤其是那些具有繁忙领域或遵循领域驱动设计的应用程序——它可能是您工具箱中非常有价值的补充,有助于保持软件易于理解和易于发展。