第 19 章 – 策略与层次
所有的软件系统都是一组策略语句的集合。软件架构设计的工作重点之一就是,将这些策略彼此分离,然后将他们按照变更的方式进行重新分组。其中变更原因、时间和层次相同的策略应该被分到同一个组件中。
我们对「层次」是严格按照「输入与输出之间的距离」来定义的。也就是说,一条策略距离系统的输入/输出越远,它所属的层次就越高。而直接管理输入/输出的策略在系统中的层次是最低的。
通过将策略隔离,并让源码中的依赖方向都统一调整为指向高层策略,我们可以大幅度降低系统变更带来的影响。因为一些针对系统低层组件的紧急小修改几乎不会影响系统中更高级、更重要的组件。
第 18 章 – 边界剖析
除单体结构以外,大部分系统都会同时采用多种边界划分策略。一个按照服务层次划分边界的系统也可能会在某一部分采用本地进程的边界划分模式。事实上,服务经常不过就是一系列互相作用的本地进程的某种外在形式。无论是服务还是本地进程,它们几乎肯定都是一个或多个源码组件组成的单体结构,或者一组动态链接的可部署组件。
这也意味着一个系统中通常会同时包含高通信量、低延迟的本地架构边界和低通信量、高延迟的服务边界。
第 17 章 – 划分边界
软件架构设计本身就是一门划分边界的艺术。边界的作用是将软件分割成各种元素,以便约束边界两侧之间的依赖关系。
「数据库相关逻辑部分是业务逻辑的具体提现」这个想法从根本上就是错误的。
界面对模型——也就是业务逻辑来说——一点都不重要。
系统的核心业务逻辑必须和其他组件隔离,保持独立,而这些其他组件要么是可以去掉的,要么是有多种实现的。
为了在软件架构中画边界线,我们需要先将系统分割成组件,其中一部分是系统的核心业务逻辑组件,而另一部分则是与核心业务逻辑无关但负责提供必要功能的插件。然后通过对源代码的修改,让这些非核心组件依赖于系统的核心业务逻辑组件。
其实,这也是一种对依赖反转原则(DIP)和稳定抽象原则(SAP)的具体应用,依赖箭头应该由底层具体实现细节指向高层抽象的方向。
实在是太难了,竟然落下了四天,争取这个周每天多补一篇,赶回来。
第 16 章 – 独立性
一个设计良好的软件架构必须支持以下几点:
- 系统的用例与正常运行
- 系统的维护
- 系统的开发
- 系统的部署
一个设计良好的架构应该通过保留可选项的方式,让系统在任何情况下都能方便地做出必要的变更。
系统可以被解耦成若干个水平分层——UI界面、应用独有的业务逻辑、领域普适的业务逻辑、数据库等。系统的用例是系统水平分层的一个个垂直切片。
我们一定要小心避免陷入对任何重复都要立即消除的应激反应模式中。一定要确保这些消除动作只针对那些真正意义上的重复。(保持系统的垂直和水平分隔,比消除重复重要。为了消除重复,破坏了系统中用例或水平分层之间的隔离,是不应该的)
解耦一个系统有多种方式:
- 源码层次(单体结构)
- 部署层次
- 服务层次(比如流行的微服务)
在项目早期很难知道哪种模式是最好的。事实上,随着项目的逐渐成熟,最好的模式可能会发生变化。一个设计良好的架构在转变解耦模式的过程中,应该能保护系统的大部分源码不受变更影响。
第 15 章 – 什么是软件架构
软件架构师自身需要是程序员,并且必须一直坚持做一线程序员。
软件架构这项工作的实质就是规划如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式。而设计软件架构的目的,就是为了在工作中更好地对这些组件进行研发、部署、运行以及维护。
软件架构的终极目标就是最大化程序员的生产力,同时最小化系统的总运营成本。
实现一键式的轻松部署应该是我们设计软件架构的一个目标。
设计良好的系统架构应该可以使开发人员对系统的运行过程一目了然。
在软件系统的所有方面中,维护所需的成本是最高的。
一个优秀的软件架构师应该致力于最大化可选向数量。
优秀的架构师会小心地将软件的高层策略与其底层实现隔离开,让高层策略与实现细节脱钩,使其策略部分完全不需要关心底层细节,当然也不会对这些细节有任何形式的依赖。另外,优秀的架构师所设计的策略应该允许系统尽可能地推迟与实现细节相关的决策,越晚做决策越好。
第 14 章 – 组件耦合 – 4
稳定抽象原则
一个组件的抽象化程度应该与其稳定性保持一致。
代表了系统高阶策略的组件应该被放到稳定组件(I=0)中,而不稳定的组件(I=1)中应该只包含那些我们想要快速和方便修改的部分。
稳定抽象原则(SAP)为组件的稳定性与它的抽象化程度建立了一种关联。一方面,该原则要求稳定的组件同时应该是抽象的,这样它的稳定性就不会影响到扩展性。另一方面,该原则也要求一个不稳定的组件应该包含具体的实现代码,这样它的不稳定性就可以通过具体的代码被轻易修改。
因此,如果一个组件想要成为稳定组件,那么它就应该由接口和抽象类组成,以便将来扩展。
将 SAP(稳定抽象原则)与 SDP(稳定依赖原则)这两个原则结合起来,就等于组件层次上的 DIP(依赖反转原则)。因为 SDP 要求的是让依赖关系指向更稳定的方向,而 SAP 则告诉我们稳定向本身就隐含了对抽象化的要求,即依赖关系应该指向更抽象的方向。跟 DIP 不同的是,在组件层面上,我们要允许一个组件部分抽象,部分稳定。
以稳定性 I 作为 X 轴,抽象化程度 A 作为 Y 轴。(0, 0) 周围的这个区域称为痛苦区,不可变组件落在这一区域是无害的。只有多变的软件组件落在这一区域才会造成麻烦。
(1, 1) 这个位置则表示这些组件是无限抽象的,而且没有被其他组件依赖,这样的组件往往无法使用。这个区域是无用区。
组件应该在主序列线(从 (1, 0) 到 (0, 1) 的线)附近,最优的位置是这条线的两端。
设计不好,需要重点分析,进行改造的组件:过于抽象而有没有多少其他组件依赖它,或者过于具体而又被依赖得太多
第 14 章 – 组件耦合 – 3
稳定依赖原则
依赖关系必须要指向更稳定的方向。
计算一个组件稳定性指标的方法:
- Fan-in: 入项依赖,这个指标指代了组件外部类依赖于组件内部类的数量
- Fan-out: 出项依赖,这个指标指代了组件内部类依赖于组件外部类的数量
- I: 不稳定性,I = Fan-out / (Fan-in + Fan-out)。该指标的范围是 [0, 1],I=0 意味着组件是最稳定的,I=1 意味着组件是最不稳定的。
稳定依赖原则(SDP)的要求是让每个组件的 I 指标都必须大于其所依赖组件的 I 指标。也就是说,组件结构依赖图中各组件的 I 指标必须要按其依赖关系方向递减。
如果有稳定组件(假设叫 Stable)要依赖易于变更的组件(假设叫 Flexible),违反了 SDP 原则,可以通过 DIP(依赖反转原则)来修复这个问题。具体步骤:
- 找到 Stable 组件中的某个类 U 需要使用 Flexible 组件中的一个类 C
- 创造一个 UServer 组件,并在其中设置一个 US 接口类
- 确保 US 接口类中包含了所有 U 需要使用的函数
- 类 C 实现 US 接口类
这样一来,Stable 和 Flexible 的依赖关系就打破了,这两个组件都依赖于 UServer 组件。
第 14 章 – 组件耦合 – 2
自上而下的设计
组件结构图是不可能自上而下被设计出来的。它必须随着软件系统的变化而变化和扩张,而不可能在系统构建的最初就被完美设计出来。
组件依赖结构图并不是用来描述应用程序功能的,它更像是应用程序在构建性和维护性方面的一张地图。
组件结构图中的一个重要目标是指导如何隔离频繁的变更。
组件依赖关系是必须要随着项目的逻辑设计一起扩张和演进的。