再读整洁架构之道(六)边界

Tommy Cheese | Jul 29, 2024 min read

什么是边界

软件架构设计本身就是一门划分边界的艺术。

边界的作用是将软件划分为各种元素,以便约束边界两侧的依赖关系。在项目初期划分的边界可以方便我们将一些决策尽量延后进行,确保未来这些决策不会对系统额核心业务逻辑造成干扰。划分边界同时可以降低或消除架构中不必要耦合,系统中存在的耦合——尤其是那些过早做出的、不成熟的决策所导致的耦合(如采用的框架,数据库等元素,这些细节应该被推迟)是系统最消耗人力资源的部分。

那么如何划分边界呢?可以遵循如下的基本原则:

  • 边界应该划分在那些无关的事情中间:如 GUI 与业务逻辑,这意味着输入输出(GUI)对于业务逻辑来说并不重要,业务逻辑可以选择多种不同的 GUI 实现;
  • 边界线应该沿着系统的变更轴来画。也就是说,位于边界线两侧的组件应该以不同原因、不同速率变化着。指导变更轴心的原则包括模块/类级别的SRP原则和组件级别的CCP原则。

在了解划分边界的重要作用,并且了解如何划分边界后,可以进一步探讨该如何划分边界了,即划分边界的基本流程:

  1. 首先将系统分割成组件,其中一部分是系统的核心业务逻辑组件,另一部分是与核心业务无关但负责提供必要功能的插件;
  2. 通过对源代码的修改,让这些非核心组件依赖于系统的核心业务逻辑组件;

划分边界是一种对依赖反转原则和稳定抽象原则的具体应用,依赖剪头应该由底层具体实现指向高层抽象的方向。

边界剖析

一个系统的架构是由一系列软件组件以及它们之间的边界共同定义的

边界具有多种形式,本章以跨边界调用为例来剖析边界。

在运行时,跨边界调用指的是边界线一侧的函数调用另一侧的函数,并同时传递数据的行为。构造合理的跨边界调用需要我们对源码中的依赖关系进行合理管控,若不对依赖管控,可能导致一个模块的源码发生变更时,其他模块的源码也可能会随之发生变更或重新编译,并需要重新部署,划清边界有助于减少这种情况的发生。

跨边界调用包含不同的类别:源码层次、部署层次、服务层次以及物理边界。

源码层次的跨边界调用

源码层次的跨边界调用出现在单体架构中,这类架构一般都需要利用某种动态形式的多态来管理其内部的依赖关系。

最简单的跨边界调用形式,是由低层客户端来调用高层服务函数,这种依赖关系在运行时和编译时会保持指向一致,都是从低层组件指向高层组件。

![image-20240729153450113](/Users/levishang/Library/Application Support/typora-user-images/image-20240729153450113.png)

但当高层组件中的客户端需要调用低层组件中的服务时,我们就需要运用动态形式的多态来反转依赖关系(图中的Service接口是某种形式的SPI)。

![image-20240729153520168](/Users/levishang/Library/Application Support/typora-user-images/image-20240729153520168.png)

部署层次的跨边界调用

部署层次的跨边界调用将其所有可部署的单元打包成一个便于操作的文件格式,除这一点以外,这种按部署层次解耦的组件与单体结构几乎是一样的。

部署层次的跨边界调用涉及到物理边界,因为不同部署的单元需要跨越物理边界来完成通信。常见的物理边界包括动态链接库、线程、本地进程等。

服务层次的跨边界调用

服务是系统架构中最强的边界形式,一个服务就是一个进程。在划分架构边界时,一定要尽可能地控制通信次数。在这个层次上通信必须能够适应高延时情况。除此之外,我们可以在服务层次上使用与本地进程相同的规则。也就是让较低层次服务成为较高层次服务的“插件”。

comments powered by Disqus