# 《架构整洁之道》读书笔记 2
# 组件
定义:是软件的部署单元,是整个软件系统可以独立完成部署的最小实体
# 拆分三原则
# REP:复用,发布等同原则
内容:软件复用的最小粒度应等同于其发布的最小粒度
# CCP:共同闭包原则
内容:将为了相同目的而同时修改的类放在同一个组件中,是SRP原则在组件层面的描述 执行:
- 对大部分应用程序而言,可维护性的重要性远远大于可复用性
- 因为一个原因需要做修改,这个修改最好在同一个组件中,如果分散在多个组件中,那么开发、提交、部署的成本都会上升
# CRP:共同复用原则
内容:不要强迫一个组件依赖它不需要的东西,是SP原则在组件层面的描述
# 三者关系
架构设计中有许多矛盾,研发性和复用性的矛盾、而研发性本身又有粘性(CCP)和排斥性的矛盾(CRP)
架构师做的往往是在这个张力图中找到一个最符合现在需要的点,而这个平衡也是不断变化的,根据项目的规模迭代的节奏等
# 依赖三原则
# 无依赖原则
互相依赖的组件,实际上组成了一个大组件,这三个组件要一起发布、一起做单元测试,通过依赖反转原则可以解依赖环
# 稳定依赖原则
内容:
- 依赖必须指向更稳定的方向,接口是最稳定的。
- 组件的稳定性,指的是组件的变更困难度,影响因素有很多,比如代码的体量大小、复杂度、清晰度等,但最最重要的一个因素就是依赖的数量一一让组件难于修改的一个最直接的办法就是让很多其他组件依赖于它!
- 组件的稳定性和它变更的频繁度没有直接的关联。或者说,稳定性可以分为价值(需求)的稳定性,和组件自身的稳定性。
定量指标:不稳定性(1)=出向依赖数量/(入向依赖数量+出向依赖数量) 方法:可以通过抽接口,共同依赖接口的方式,修正违反稳定依赖的地方
# 稳定抽象原则
内容:
- 一个组件的抽象化程度应该与其稳定性保持一致
- 为了防止高阶架构设计和髙阶策略难以修改,通常抽象岀稳定的接口或抽象类。越稳定的库就应该越抽象,这样它的稳定性就不会影响它的扩展性
定量描述:
- 抽象程度(A)=组件中抽象类和接口的数量/组件中类的数量
- 将不稳定性和抽象程度分别作为横轴和纵轴,画一个二维的图,(0,1)(1,0)连线就是主序列线。靠近(0,0)的区域是痛苦区,改动成本很大,但是又很具体。靠近(0,0)的是无用区,非常抽象,但是没有别的组件依赖它改动成本很小,通常是废弃的。
- 离主序列先的距离D=|A+1-1L可以定量化的衡量一组件的健康程度。在D满足期望的条件下,约靠近(0,1)和(1,0)越好
# 封装方式
- 按层封装,也就是传统的水平分层架构
- 按功能封装,即垂直切分,根据相关的功能、业务概念或者聚合根来切分
- 端口和适配器
- 按组件封装
# 软件架构
# 目的
终极目的:最大化程序员的生产力,最小化系统的总运营成本 细化目的:支撑软件系统的全生命周期,让系统便于理解、易于修改、方便维护、轻松部署
# 方针
- 尽可能长时间地保留尽可能多的可选项
- 具体选用哪个存储方式,或哪个数据库
- 数据库:擅长于内推查询
- 文件:擅长于文件快速查找和整体读取 如果硬盘被淘汰时,用什么存储系统差别不大
- 使用哪种web服务
- 使用哪种框架
- 框架的使用文档是开发者角度写的,他自然吹嘘自己能力,希望你完全耦合他们的框架
- 存在风险:
- 产品发展,框架不再满足需求
- 框架本身朝着我们不需要的方向演进
- 未来我们可能希望迁到一个新的更好的框架上
- 具体选用哪个存储方式,或哪个数据库
- 边界约完善,开发和部罟成本越高。所以不完全边界能决的,不要用完全边界,低层次解耦能解决的,不要用层次解耦
# 内容
- 组件拆分 组件是一组描述如何将输入转化为输出的策略语句的集合,这些策璐的变更原因、时间、层次相同
- 切分
- 水平分层 一条策略距离系统的输入,输出越远,他的层次就越高 例子:
- UI 界面
- 应用独有业务逻辑
- 领域普适业务逻辑
- 存储
- 按用例垂直切分 每个用例几乎涉及到所有的水平分层,如何做到新加用例,不影响旧的用例(比如:订单聊天)
- 重复 如果两段代码,看起来重复,但是走的是不同的滨进路径,就不是真正的重复
- 解耦模式
从上到下,(开发、部署)成本依次升高,如果低层次的解耦已经满足需要,不要进行高层次的解耦
- 源码层次 做了接口、类依赖上的(不完全的)解耦,但是放在同个组件中,通常放在不同的路径下
- 部署层次 仍然运行在同一个机器上,彼此通过函数调用通讯运行在不同的机器上,通过ur、网络数据包等方式进行通讯
- 服务层次 服务不等同于模块,比如横跨型变更需要改动所有服务,但是可能并不会改动架构
- 组件排列(依赖)
依赖关系与数据流控制流脱钩,与组件所在层次挂钩,所以组件的依赖与组件水平分层息息相关
- 业务实体
- 包含关键业务数据和业务逻辑
- 与界面无关、与存储无关、与框架无关,只有业务逻辑没有别的
- 用例
特定场景下的业务逻辑
- 三要素
- 需要用户提供的输入数据(注意解耦输入方式,这里只关心数据)
- 用户应该得到的输出数据(注意解耦输出方式,这里只关心数据)
- 从输入数据到输出数据,应该采取的处理步骤 不要把业务实体直接当做输入数据对象或者输出数据对,因为他们会以不同的原因和速率发生变更
- 三要素
- 接口适配器 整个MVC,对存储,设备,界面等接口的声明与使用
- 框架与驱动程序
- 因为与硬件太相关的部分,比如用户界面,是不可测的,所以这里的边界处理通常使用谦卑对象模式
- 谦卑对象要有自知之明,简化到不能再简化,不应该包含对数据的任何处理。数据处理全部放到接口适配器(比如视图模型)中
- 测试层
- 测试也是一个组件
- 测试关键之处是耦合,测试如何依赖所有其他组件的所有接口,那测试就是脆弱的,任何改动都引起n个case失效
- 解法是给测试层单独写一套特有的API
- 业务实体
- 组件通信
- 方式:(接口调用,服务调用)
- 完全边界
- 调用双方都声明接口
- 专用的输入数据类型
- 专用的返回数据类型
- 不完全边界
- 省掉最后一步 保留到源码层次的解耦,声明好接口,做好分割后,仍然放在一个组件中。等到时几成熟时再拆出来独立编译部署
- 单向边界 正常的切割,应该使用两个接口,两个类各自使用对方的接口,而不是直接使用类,但是这样的开发成本很大,所以,只实现一个接口,高层用接口调用低层,而低层直接使用高层的类
- 门户模式 控制权的间接转移不用接口和实现去做,而是用门户类去故,接口都不用声明了
# 软件的生命周期
- 开发
- 不同团队负责的组件不交叉
- 不使用大量复杂的脚手架
- 部署
- 减少组件数量,内部组件外部组件结合的方式
- 不依赖成堆的脚本和配置文件
- 运行
- 这方面的价值对架构的影响最小
- 不同吞吐量、不同的响应时长要求,是架构设计要考虑的点,采用微服务?单线程?多线程
- 架构应起到揭示系统运行的作用:用例、功能、行为设置应该都是对开发者可见的一级实体,以类、函数或模块的形式占据明显位置,命名能清晰地描述对应的功能
- 维护
- 探秘成本:对现有软件系统的挖掘,确定新功能或修复问题的最佳位置和方式
- 风险成本:做改动时,可能衍生出新的问题