第 29 章 – 整洁的嵌入式架构
对于程序员来说,让他的程序工作这件事只能被成为「程序适用测试」。(仅仅是「可用」)。
整洁的嵌入式架构就是可测试的嵌入式架构。
软件与固件之间的边界被成为硬件抽象层(HAL)。HAL 的存在是为了给它上层的软件提供服务,HAL 的 API 应该按照这些软件的需要来量身定做。
不要向 HAL 的用户暴露硬件细节,依照整洁的嵌入式架构所建构的软件应该是可以脱离目标硬件平台来进行测试的。
整洁的嵌入式架构会引入操作系统抽象层(OSAL),将软件与操作系统分隔开。(固件跟操作系统之间有 HAL,操作系统与软件之间有 OSAL)。
- 面向接口编程,每个分层都可测。
- DRY 条件性编译命令(使用 HAL)
第 28 章 – 测试边界
测试代码也是系统的一部分。
测试组件也是要遵守依赖关系原则的。
软件设计的第一条原则——不管是为了可测试性还是其他什么东西——是不变的,就是不要依赖于多变的东西。譬如,GUI 往往是多变的,因此通过 GUI 来验证系统的测试一定是脆弱的。因此,我们在系统设计与测试设计时,应该让业务逻辑不通过 GUI 也可以被测试。
测试专用 API 应该成为用户界面所用到的交互器和接口适配器的一个超集。设置测试 API 是为了将测试部分从应用程序中分离出来,将测试代码的结构与应用程序其他部分的代码结构分开。
测试专用 API 的作用就是将应用程序与测试代码解耦。
按系统组成部分来设计测试代码。
第 27 章 – 服务:宏观与微观
所谓的服务,只是一种比函数调用方式成本稍高的,分割应用程序行为的一种形式,与系统架构无关。
服务之间的确在变量层面做到了彼此隔离。然而,它们之间还是可能会因为处理器内的共享资源,或者通过网络共享资源而彼此耦合的。另外,任何形式的共享数据行为都会导致强耦合。
服务化并不是构建大型系统的唯一选择。拆分服务并不意味着这些服务可以彼此独立开发、部署和运维。
服务边界并不能代表系统的架构边界,服务内部的组件边界才是。
系统的架构是由系统内部架构边界,以及边界之间的依赖关系所定义的,与系统中各组件之间的调用和通信方式无关。
第 26 章 – Main 组件
Main 组件是系统中最细节化的部分——也就是底层的策略,它是整个系统的初始点。
它处于整洁架构的最外圈(离最核心逻辑最远),主要负责为系统加载所有必要的信息,然后再将控制权转交回系统的高层组件。
Main 组件也可以被视为应用程序的一个插件——这个插件负责设置起始状态、配置信息、加载外部资源,最后将控制权交给应用程序的其他高层组件。另外,由于 Main 组件能以插件形式存在于系统中,因此我们可以为一个系统设计多个 Main 组件,让它们各自对应于不同的配置。
第 25 章 – 层次与边界
架构边界可以存在于任何地方。作为架构师,我们必须要小心审视究竟在什么地方才需要设计架构边界。另外,我们还必须弄清楚完全实现这些边界将会带来多大的成本。
同时,我们也必须要了解如果事先忽略了这些边界,后续再添加会有多么困难——哪怕有覆盖广泛的测试,严加小心的重构也于事无补。
软件架构师必须仔细权衡成本,决定哪里需要设计架构边界,以及这些地方需要的是完整的边界,还是不完全的边界,还是可以忽略的边界。
我们的目标是找到设置边界的优势超过其成本的拐点,那就是实现该边界的最佳时机。
第 24 章 – 不完全边界
三种不完全地实现架构边界的简单方法:
一种方式是将系统分割成一系列可以独立编译、独立部署的组件之后,再把他们构建成一个组件。省去了多组件管理这部粉的工作,但是随着时间推移,各个组件间的隔离可能会弱化。
使用「策略模式」,完成必要的依赖反转,实现单向的边界。但是到了后期,如果还没有变成双向的隔离,组件间的隔离就要靠开发者和架构师的自律性来保证了。
采用「门户模式」,连依赖反转也不必做了,就可以完成隔离。但是会有传递性的依赖。
架构师的职责之一就是预判未来哪里有可能会需要设置架构边界,并决定应该以完全形式还是不完全形式来实现他们。
第 23 章 – 展示器和谦卑对象
「谦卑」在这里是拟人化的,指难以测试的对象清晰地认识到自己的局限性,只发挥自己的桥梁和通信作用,并不从中干预信息的传输。
利用谦卑对象模式,将 GUI 拆分成展示器和视图两部分,视图部分属于谦卑对象。
这种拆分,常常就定义了系统的架构边界。
数据库网关和交互其之间有边界,数据库网关属于谦卑对象。
在每个系统架构的边界处,都有可能发现谦卑对象模式的存在。因为跨边界的通信肯定需要用到某种简单的数据结构,而边界会自然而然地将系统分割成难以测试的部分和容易测试的部分,所以通过在系统的边界处运用谦卑对象模式,我们可以大幅地提高整个系统的可测试性。
第 22 章 – 整洁架构
按照这些架构(六边形架构、DCI 架构、BCE 架构)设计出来的系统,通常都具有以下特点:
- 独立于框架
- 可被测试
- 独立于 UI
- 独立于数据库
- 独立于任何外部机构
以几个同心圆画出来的整洁架构示意图,从里到外,各层分别是
- 业务实体(系统级业务逻辑)
- 用例(应用级业务逻辑)
- 控制器、展示器、网关(接口适配器)
- Web、用户界面、数据库、设备、外部接口(框架与驱动程序)
依赖关系规则
源码中的依赖关系必须只指向同心圆的内层,即由低层机制指向高层策略。
业务实体
整个系统的关键业务逻辑。
系统中最不容易受外界影响而变动的部分。
用例
通常包含的是~特定应用场景~下的业务逻辑。
用例引导数据在业务实体之间的流入/流出,并指挥着业务实体利用其中的关键业务逻辑来实现用例的设计目标。
接口适配器
负责将数据从对用例和业务实体而言最方便操作的格式,转化成外部系统(譬如数据库以及 Web)最方便操作的格式。
譬如,如果我们采用的是 SQL 数据库,那么所有的 SQL 语句都应该被限制在这一层的代码中——而且是仅限于哪些需要操作数据库的代码。
框架与驱动程序
一般是由工具、数据库、Web 框架等组成的。在这一层,我们通常只需要编写一些与内层沟通的黏合性代码。
真正的架构很可能会超过四层。并没有某个规则约定一个系统的架构有且只能有四层。
当我们进行跨边界传输时,一定要采用内层最方便使用的形式。
第 21 章 – 尖叫的软件架构
软件的系统架构应该为该系统的用例提供支持。这就像住宅和图书馆的建筑计划满篇都在非常明显地凸显这些建筑的用例一样,软件系统的架构设计图也应该非常明确地凸显该应用程序会有哪些用例。
一个良好的架构设计应该围绕着用例来展开,这样的架构设计可以在脱离框架、工具以及使用环境的情况下完整地描述用例。
框架应该是一个可选项。
第 20 章 – 业务逻辑
关键业务逻辑和关键业务数据是紧密相关的,所以它们很适合被放在同一个对象中处理。我们将这种对象称为「业务实体(Entity)」。
用例本质上就是关于如何操作一个自动化系统的描述,它定义了用户需要提供的输入数据、用户应该得到的输出信息以及产生输出所应该采取的处理步骤。
业务实体属于高层概念,而用例属于底层概念。用例更靠近系统的输入和输出。
业务逻辑应该保持纯净,不要掺杂用户界面或者所使用的数据库相关的东西。在理想情况下,这部分代码业务逻辑的代码应该是整个系统的核心,其他底层概念的实现应该以插件形式接入系统种。业务逻辑应该是系统中最独立、复用性最高的代码。