【读书笔记】架构整洁之道

价值维度

  行为价值:软件的行为价值,实现了需求,该需求的价值;对于业务方来说,完成业务最重要;

  架构价值:持续的质量所带来的好处,因为需求是会变的,业务方会认为每次需求都是同等的代价投入,但如果架构不好,那么投入的成本会很大,这种架构的价值就非常低,甚至是负的

SOLID原则

  单一职责原则:任何一个软件模块都只应该对某一类行为者负责;这个原则从 行为上聚合逻辑、或者分离逻辑

  开闭原则:设计良好的计算机系统应该易于被拓展,同时抗拒修改;一个好的软件架构设计师会努力将旧代码修改需求量降至最少,甚至0;

  • 实现方式:把系统分为逻辑相对独立的几个组件,然后控制这些组件的依赖关系(依赖倒置),使得高层组件不依赖底层组件;

  里氏替换原则:接口的实现逻辑要保证其调用者的可替换性;替换后不影响调用者的逻辑;

  接口隔离原则:不要让系统依赖他们不使用的东西,会带来想不到的意外;从使用者角度,去分离逻辑

  依赖倒置原则:我们更应该多依赖稳定的抽象,而不是具体的实现;分离抽象层和实现层,让抽象层依赖抽象层;实现层依赖抽象层;

  • 依赖倒置,很多时候是为了开闭原则服务。

组件

  高层组件与底层组件:离细节最近的组件是底层组件,底层组件是容易变化的,例如输入输出就需要具体化,离策略,特别是抽象稳定、通用的策略越近的组件,就是高层组件。

组件设计

  组件是软件部署的基本单元:

  什么类或者逻辑、文件应该被放到一个组件中部署呢?

  复用/发布等同原则:软件复用的最小粒度应该等同于其发布的最小粒度,所以组件的复合应该以共同发布为基础,并且要有版本号的概念;

  共同闭包原则:我们应该要以共同修改,并且以相同目的修改的类放在一起成为一个组件;并以不同目的的类分开;其实是单一职责原则的组件版本;

  共同复用原则:不要强迫用户依赖他们不需要的东西,这个原则看起来和共同闭包原则相关,但它侧重的是:如何把类分开,这个就有点像依赖倒置原则了;

组件耦合

  有了组件和版本号的合作之后,研发合作可以选择自己依赖的组件的版本,所以并不会说依赖别人代码(直接依赖),别人改了倒置你的代码无法跑,但这样的好处必须要保证,整个系统的组件是无循环依赖的;

  无循环依赖原则:组件应该保证无循环依赖,循环依赖会导致很多修改问题;

  其实组件依赖结构图,是并不是用来描述应用程序功能的,它更像是程序构建和维护的一张地图;例如,本来是有A依赖B,但为了倒置依赖,出现了A依赖C,B也依赖C的情况,那么C这个组件就是“抖动”出来的;因此

  • 组件架构图是无法在一开始被设计出来的
  • 组件的依赖关系,应该是一种拓扑结构

  稳定依赖原则

  依赖关系必须指向更稳定的地方,通过共同闭包原则,我们能把易变性和不易变性组件区分开来,通过思考可变性因素和不可变因素,我们组件的设计依赖方向,必须要更多指向不易变组件,这样让不易变组件变得更加稳定;

  另外一方面而言,被依赖的组件,通常非常“稳定”,因为你不敢改它;

  抽象组件:当一个组件是属于协议、接口类组成,那么这些组件通常叫抽象组件

  稳定抽象原则:一个组件的抽象化程度应该与其稳定性保持一致;越是稳定,抽象程度应该越高;

  • 其实把稳定抽象原则和稳定依赖原则结合,就是组件层面的依赖倒置原则;

架构设计

  如果要设计一个便于推进各项工作的系统,那么其策略应该是在系统设计过程中尽可能多的保留可选项

  架构师必须要是一线程序员,不经历设计的痛苦不可能成为优秀的设计师

  架构设计因子开发、部署、运行、维护

  • 开发的团队,应该要和架构的设计保持一致
  • 架构的设计,应该要考虑部署,易于部署,以及难部署的时候的其他设计方案
  • 运行性要求也会影响架构设计,而且应该可视化架构的运行过程,让人可易于了解架构
  • 维护有两个成本,“探秘”,“风险”
    • 探秘是指修改系统的时候,找到最佳修改位置,所需要的理解成本
    • 风险,是修改系统后,所可能因为出其他新的问题

  策略与细节分离:设备无关性,保留更多的可选项

  • 策略是指业务逻辑,以及操作过程,是体现系统的真正价值所在
  • 细节是不会影响策略本身的行为,例如:数据库、访问方式、磁盘IO、框架、交互协议

独立性

  一个设计良好的软件架构必须支持一下几点:用例、运行、开发、部署、维护,然后在各种情况下保持可选项;做法是考虑这些不同点而设计出的具有各种 独立性 的组件;

  用例:一个系统的架构必须是看起来像其设计意图,所以没有说万能的系统架构,购物车系统看起来肯定是购物车,其中会体现在类、组件等的名字中;用例在架构层面上可见是很重要的

  运行:追求吞吐量,还是追求响应时间,这是运行时候的非功能需求,当然这也会是变化的,因此:架构设计可以区分过程和调度,策略和细节,使得可以随时根据不断的变化需求来转换成各种运行的线程、进程、或服务模型;让这些成为未来的可选项;面向对象是过程的抽象,线程是调度的抽象,就很重要了

  开发:任何一个组织在设计系统的时候,往往都会复制出一个与该组织内部沟通结构一相同的系统;所以,保证团队和架构设计的一致性,很重要。

  部署:这个也是正确划分,通过源码上隔离系统组件来实现,这其中包括开发一些主组件,让他们讲整个系统粘合在一起,正确的启动、链接并监控每个组件;技术设施完善的大公司,这方面往往就不需要关心太多

  独立性:要求保留可选项

  我们要在设计组件过程中,为了独立性,需要考虑以上所有因素然后保持最多的可选项,这很难,因为我们无法预知有多少用例,团队结构,可选的部署要求等,而且这些就算知道,将来也是可变的,但我们可以用设计原则来提前解决一些平衡问题,设计为独立的,隔离性高的组件,尽可能长时间的为我们保留可选项;那么都有些什么方法呢?

  按层解耦合:单一职责原则、共同闭包原则,告诉我们用户界面的变化原则,和业务逻辑的变化原则肯定是不同的,类似这种,我们可以很好的可以按需要水平分层;界面层、业务层、数据库层、应用层;正如有一句话可以说,其实软件领域,没有什么问题是加一层无法解决的;

  • 应用独有的业务逻辑层
  • 普适性的业务逻辑层(common包)
  • UI层
  • 数据库层

  用例的解耦:用例本身也有因为不同原因而变化的逻辑,揭开这些逻辑后,我们通常可以做到开闭开发,这种独立性的划分,我们通常叫:垂直的划分

  源码的解耦:如果两个组件之间,是通过进程之间的方式通讯,那么这种就是可以不在一个部署单元上,也非单体结构,这就是源码上的解耦;源码上的解耦保证了部署的独立性

  独立性:好处

  运行的好处:按照这样解耦,系统的运行将会受到有利支持,例如高吞吐量和低吞吐量的、实时性高的用例解耦,他们对应的组件可以分开部分,选择不同配置的云主机,以便更好的发挥性能;

  开发的好处:不同独立性的组件,这样开发团队会更独立,效率更高,把组件分给前端、不同的领域后端团队,不同的数据库专家团队;

  部署的好处:例子:可以很好做到热部署,插件式开发;

  独立性:注意重复

  架构师最怕钻进的牛角尖,害怕重复性,要注意区分:真重复和假的重复,例如界面DTO和数据库DO之间很多字段重复的,但是他们最好不用用一个类表示,因为很多这种情况都是假的重复,他们的演变方向是不一样的;

  考虑一下未来的演变,就可以很好的区分是否假的重复。

边界艺术

  软件是一项划分边界的艺术。边界的作用是将软件划分为各种不同的元素,并约束他们之间的关系。边界的划分可以是初期就划分,也可以是后期演进出来的;

  架构师的职责是使得维护一个系统所需的人力资源最小,而对维护产生的资源有最大影响的,就是耦合;

  IO边界(IO无关性):在业务用例中,业务模型是很重要的,但是输入输出的不重要的,我们应该在IO和业务用例之间,划分一条边界,做到IO无关性。

  功能边界(插件式架构):软件开发的发展是如何想方设法方便的增加插件,从而构建一个可拓展、可维护的系统架构;界面和数据库也可以是插件的一种、电脑的插件就更多了,耳机、键盘、鼠标都是插件。

  单体结构:边界之间的耦合,单体内的组件是最容易耦合的,因为他并没有做到源码上的解耦。同时也应该注意到,即时在单体结构内,做好组件的边界划分和解耦也是必要的,单体应该能做好随时支持部署上的解耦的作用

  • 单体结构的组件解耦,由某种形式上的多态(特别是反转依赖)来实现,这也是面向对象模式一直是重要的编程范式的原因之一。
  • 线程不在组件的范畴,而是调度的范畴;但是写代码的时候,确实也是需要考虑调度的问题。

  策略边界:本质上,所有软件系统都是一组策略语句的集合。可以说,计算机程序不过就是一组仔细描述如何将输入转化为输出的策略语句的集合。

策略彼此分离,按照变化的时间、原因、层次相同的策略划分在一起。

  层次边界:既然计算机是输入输出的处理器猫,那么可以按照对“输入输出的距离”来定义层次的高低,从而划分不同的层次边界,从而让低层次的设计为依赖高层次的组件。

业务逻辑

  业务逻辑是那种程序中真正用于赚钱或者省钱的过程,无论业务逻辑是在计算机上实现的,还是人工执行的,他们在赚钱上的作用都是一样的。

  业务实体:我们应该能识别出那些 关键的业务逻辑 和 关键的业务数据,但找到逻辑和数据紧密关联的存在后,我们就可以创造出:业务实体

  • 业务实体应该只包含业务逻辑,没有别的,做到与数据库、用户界面、第三方框架无关
  • 业务逻辑不一定要用面向对象编程语言的类来实现

  用例:不是所有的业务逻辑都是可以组织称 业务实体,有些业务逻辑是通过定义或者限制自动化系统的运作方式来来实现赚钱或者省钱的业务的,我无法定义,但专业人士应该知道,他叫 用例

  • 用例应该是某种场景下的业务逻辑,但很少会是关键业务逻辑。
  • 用例定义了用户的输入、系统的运作步骤和用户得到的输出。
  • IO无关性)用例,同样的,他应该与数据流入流出系统的方式无关

  层次关系:业务实体比用例的层次更高,因为业务实体不限定场景,而用例是特定场景,而且离输出输入更近

  • 所以在请求响应模型中 ,最好不要在app层直接引用实体,他们存在的意义是不同的,演进原因和速度也不一样。

尖叫的软件架构

  架构的主题:软件的系统架构,应该为系统的用例提供支持,图书馆应该看起来像是图书馆,家应该看起来像家,而不是基于某些框架建设。

  核心目标-用例:架构设计的核心目标,应该是围绕用例来展开的,不管是分布式、实时性、高吞吐等因素,而且应该在满足用例的情况下,尽可能的保证其他因素保持隔离,让他们属于都是属于可选项,

  WEB:WEB是一种交付工具;

  框架:框架只是一种工具,框架开发者会认为自己的框架是万能的,这不应该成为你的观点

  可测试:当你围绕着用例展开,那么我们可以不依赖任何框架来针对这些用例进行单元测试。

整洁架构

  整洁的架构有着 完整的系统边界,它的特点:

  • 独立于框架
  • 可被测试
  • 独立于UI
  • 独立于数据库
  • 独立于所有外部系统 

 

 

  依赖关系:越是中心,软件层次越高,外圆是机制,内圆是策略:源码中的依赖关系,必须指向同心圆的内层,即由底层机制指向高层

展示器和谦卑对象

  谦卑:谦卑这里是拟人化的,指的是难以测试的对象清晰地认识到自己的局限性,只发挥自己的桥梁和通信作用,并不从中干预信息的传输。

  视图与展示器:视图是谦卑对象,它反应的是难以测试的GUI,展示器则是可测试的对象;

  数据库网关:数据库网关通常是一个多态接口,而数据库的实现则是谦卑对象,因为他们难以测试;

  数据映射器:ORM,其实不存在对象关系映射器,我们更应该叫做:数据映射器;ORM应该是在数据库网关接口和数据库之间构建了另一种谦卑对象的边界

  每个系统架构的边界,都有可能发现谦卑对象模式的存在,因为跨边界的通讯肯定需要用到某种简单的数据结构,二边界会自然而言的把系统分割为难以测试的部分与容易测试的部分,所以在系统邪恶边界处运用谦卑对象模式,可以大幅度提供系统的可测试性,。

不完全边界

  类似整洁架构,不同的系统的组合,都由转换器进行,所以是数据完整的系统边界,但构建完整的的系统边界是很费时费力的,所以我们有一种方案,是构建不完全的系统边界。

  省掉最后一步:可以在源码上划分组件边界,但是依旧把组件放在一起部署,为以后划分做好准备,现在的模块划分就很好用。

  单向边界:可以利用倒置依赖,组成一个单向的系统边界,同时被依赖的抽象,就隔离了两个边界。

  门面模式:门面模式不同于单向边界,它没有倒置依赖,只是做了一层调用封装,隐藏细节,这种隐藏也是一种很好的不完全边界

Main组件

  构建与运行分离:在我们系统中,都至少要有一个组件来负责创建、协调、监督其他组件的运转,我们将其称为Main组件,而在Spring上,可以理解为对应的配置、bean注册等工作,这些都属于一个程序的构建工作。

  细节:Main组件是细节化的部分,也就是最底层的策略,它是整个系统的初始化点,任务是创建所有的工作类、策略类、以及全局基础设施,并构建他们之间的关系,最终把系统的控制权交给最高抽象层的代码来处理。

  Main插件:Main组件也可以视为一种插件,该插件负责设置起始状态,配置信息,加载外部资源,由于可以设置为插件,所以一个系统可以有多个main插件,让他们各自应对不同的配置。可以针对测试环境、生产环境、不同国家、地区等区分

测试边界

  测试也是一种组件,测试代码也应该是系统的一部分;测试组件的存在是为了支持开发过程,而不是运行过程,测试组件也应该是可以独立部署的;

  脆弱的测试设计:如果修改一个通用的组件,会导致成百上千个测试出现问题,我们通常会将这类问题称为:脆弱的测试问题。所以我们必须要考虑系统的可测试性,总的而言就是不要依赖 多变的东西。例如测试不依赖GUI

  测试专用API:设计一个可测试系统的方法之一,就是为组件创建一个测试专用API,这个API应该有超级管理员权限。

  结构性耦合:一个系统中不同的对外服务产品,都有对应的测试组件,那么就会形成结构性耦合,我们应该设计一个测试专用API,用作测试内部组件,而不依赖产品的多样性。

  安全性:我们要隔离这种测试专用API到一个组件中,这是最好的做法,避免在生产环境上产生安全问题。

 

posted @ 2022-02-10 01:28  饭小胖  阅读(299)  评论(0编辑  收藏  举报