likes
comments
collection
share

保持架构整洁-读《架构整洁之道》

作者站长头像
站长
· 阅读数 2

[美] Robert C.Martin

保持架构整洁-读《架构整洁之道》

一句话总结

鲍勃大叔封神之作!!! 架构要解决的核心问题是保持程序员的高产能,使业务迭代速度不随着时间推移(代码复杂度变高或者人员变动等因素)而变慢。具体表现在:代码一目了然,新手开发者在不借助外力前提下知道改哪儿,怎么改。本书系统地介绍了架构的相关知识,基于整洁架构的洋葱模型对工程代码进行分层从而实现关注点分离。具备这些理论知识在工程实践中可以大幅降低代码维护成本,让工程跑的更“稳”,毕竟跑的“稳”就是跑的“快”。

脑图

保持架构整洁-读《架构整洁之道》

详情

第1章:设计与架构究竟是什么

  • 名词解释
    • 设计(Design):一般用于低层次的讨论中,表示系统底层的组织结构与实现细节
    • 架构(Architecture):一般用于高层次的讨论中,形而上地看待系统
  • **设计与架构一点区别都没有!!!**所谓的底层跟高层本身就是一系列决策组成的连续体,没有清晰的分界线。任何架构的实施都离不开细节代码
  • 软件架构的最终目标:用最小的人力成本来满足构建和维护该系统的需求
  • 软件开发的核心特点:要想跑得快,先得跑的稳
  • 即使现有架构很烂,也不要轻易全盘否认现有架构去重构。过度自信的重构设计只会陷入和原项目相同的困境中。

第2章:两个价值维度

  • 软件系统有两个实际的价值:行为、架构
    • 行为价值:程序员将需求进行编码与调试,软件可以成功运行起来为用户服务
    • 架构价值:软件必须足够“软”,需求方改变需求时变更的实施难度应该与变味的范围(scrope)成等比关系,与实施的形状(shape)无关。PS:如果架构设计的不好,项目前期让系统倾向于某种“形状”,那后期的实施形状就会极大地限制生产力
  • 哪种价值维度更重要:业务部门肯定会说行为价值最重要,很多人的观点就是业务需求紧急,先 run 起来再说。但是如果真的不管架构价值,后期需求变更的时候业务方就会吐槽为啥变更这么难。SO:架构价值与行为价值一样重要,不要只听业务部门的一面之词,要有工程师自己的判断
  • 艾森豪威尔矩阵:“我有两种难题,紧急的和重要的,紧急的永远不重要,重要的永远不紧急”
    • 保持架构整洁-读《架构整洁之道》
    • 把艾森豪威尔矩阵映射到软件系统中来看:
      • 行为价值:紧急的但是并不总是重要的
      • 架构价值:重要的但是并不总是紧急的
    • 工作时我们要按照事情的重要程度进行排序:重要且紧急 > 重要不紧急 > 不重要但紧急 > 不重要也不紧急。
  • 与业务方去PK保护架构价值是工程师的职责:如果忽略软件的架构价值,那软件就变的不那么“软”,系统再也难以修改。造成这种局面的原因是工程师团队没有与需求方做足够的抗争,没有尽责~

第3章:编程范式总览

  • 结构化编程:对程序控制权的直接转移进行了限制与规范——限制 goto 语句的使用
  • 面相对象变成:对程序控制权的间接转移进行了限制与规范——限制函数指针的使用
  • 函数式编程:对程序中的赋值进行了限制与规范——限制赋值语句的使用
  • 综上:编程范式没有新能力的增加,都是对现有能力进行了规范与限制

第4章:结构化编程

  • Dijkstra(荷兰人) 在计算机刚诞生的时候就总结了结论:编程是一项难度极大的活动。他的解决方案是采用数学的推导方法(公理、定理、推论、引理)去降低编程的复杂性。即:程序员可以用代码将一些已经证明的结构串联起来,只要自己证明额外的代码是正确的就可以推导出整个程序是正确的。(顺序、分支、循环等结构就是已经被证明的正确的结构)
  • 功能性降解拆分:按照推理方法,程序员可以将模块递归地拆分成更小的模块,最后都可以拆分成可被证明的函数
  • 软件类似于现代科学理论与科学定律:它们可以被证伪,但不可以被证明(比如π是3.141592654...,只要现在大家还没举出反例,那这个值就是对的)。软件也一样,只要现在没有证明有 Bug 那软件就是可以用的
  • 结构化编程的功绩是程序可被功能性降解拆分,在架构领域也参考了结构化编程的思想进行模块化拆解

第5章:面相对象编程

  • 对程序控制权的间接转移:在没有面向对象编程语言的时代(C时代),程序员实现面向对象编程是靠程序员的个人约定拿函数指针去模拟对象。这对开发者的要求甚高并且容易出现异常的 bug。面相对象语言夺回了函数指针的使用权让多态变得简单。
  • 通过多态,架构设计上可以实现依赖反转 DIP (程序源码的依赖关系不再受系统控制流方向的限制)!!!
    • 简单理解:在多态之前,源码的依赖方向受控制方向限制,A 模块要调用控制 B 模块,A 就要依赖 B,不然没法调。这就造成了 A 与 B 的耦合。有了多态,A 调用 B 的时候可以调用接口 C ,B 实现 C。这时候依赖流方向与程序控制流相反,就是依赖反转!(程序运行时的依赖关系与编译时是相反的)
保持架构整洁-读《架构整洁之道》
  • 面对对象编程就是以多态的手段对源码中的依赖关系进行控制,让架构师可以依赖抽象而不是细节从而设计出插件式架构

第6章:函数式编程

  • 函数式编程将赋值语句进行了屏蔽,函数式系统中的变量是不可变的,这就提升了系统的稳定性
  • 不可变性对软件架构的设计具有重大意义:所有与并发相关的问题如果没有可变变量都不可能发生
  • 可变性的隔离:
    • 不可变组件用纯函数的方式来执行任务,期间不更改任何状态
    • 一个架构设计良好的应用程序应该将状态的修改部分和不需要修改的部分隔离成单独的组件,将大部分的逻辑放到不可变组件中,可变组件逻辑越少越好
  • 事件溯源:类似于 git ,git 并不保存工程所有文件记录,仅仅保存了文件的变更记录,每次用户想回滚的时候只需要把 commit 的变更执行一遍就行了

第7章:SRP:单一职责原则

  • SRP原则解释:任何一个软件的模块都应该有且只有一个被修改的原因。
    • 也可以这样解释:任何一个软件模块都应该只对某一类行为者负责。行为者:一个或多个用户对系统进行变更的原因是相似的(即多个团队维护同一个模块也是违反 SRP 原则的体现)
    • 注意:每个模块只应该做一件事的解释是错误的!
  • 一些案例
    • 案例1:不同行为者依赖的代码要进行拆分,比如财务模块与CEO模块都依赖工资算法,这个工资算法就应该被拆分
    • 案例2:服务不同行为者的代码要进行拆分
  • SRP讨论的是类与函数之间的关系,但是在不同的维度上也体现着不同的形式
    • 组件层面(哪些类应该放在同一个组件中):也被称为共同闭包
    • 软件架构层面:奠定架构边界的变更轴心

第8章:OCP:开闭原则

保持架构整洁-读《架构整洁之道》
  • OCP原则解释:软件应该易于扩展,抗拒修改
  • 架构上对 OCP 原则的应用:将系统划分成一系列的组件(按照函数被修改的原因、方式等维度),将这些组件的依赖关系按照层次结构进行组织,使得高阶组件不依赖低阶组件
    • 用 DIP 去反转以来方向
    • 用接口进行信息隐藏,避免依赖传递(软件系统不应该依赖其不直接使用的组件)
    • 高阶组件:业务核心逻辑
    • 低阶组件:UI等实现细节
  • 架构图上的依赖都应该是单向的,箭头指向那些不经常更改的组件

第9章:LSP:里氏替换原则

  • LSP解释:一种可替换性,如果对于每个类型都是S的对象 O1都存在一个类型为 T 的对象 O2,能使操作 T 类型的程序 P 在用 O2 替换 O1 时行为保持不变,我们就说 S 称为 T 的子类型
  • LSP 是一种指导接口与实现方式的设计原则,注意:这里说的接口不一定是 Java Interface 与实现类的关系,还可以像 Ruby 一样,几个类同用一样的方法签名,甚至是几个具有相同行为的 REST 服务
  • 一旦违反了 LSP ,软件系统中就多了很多复杂的应对机制(比如大量的 if else ),让系统扩展变得更困难

第10章:ISP:接口隔离原则

保持架构整洁-读《架构整洁之道》
  • ISP:任何层次的软件实际如果依赖了它不需要的东西,就会带来意料之外的麻烦。
    • 软件架构只应该依赖需要依赖的东西,如果被依赖的模块很大,那就应该拆分成多个接口。依赖者只依赖需要的接口

第11章:DIP:依赖反转原则

  • DIP:如果要设计一个灵活的系统,在源码层次的依赖关系中就应该多依赖抽象而非具体实现
    • 不是所有模块都需要遵循 DIP。我们应该关注的是软件系统内部那些经常变更的模块,这些模块总是在不停地开发,所以变更也会经常出现。最起码 Main 函数负责组装整个系统时就必须使用具体的实现
  • 稳定的抽象层:架构师会花费大量的时间去设计稳定的抽象接口,未来扩展功能的时候只需要去实现新的细节就可以了。争取自不修改接口的前提下扩展系统功能是软件设计的基础常识。下面列举一些实践原则:
    • 应该在代码中多使用抽象接口,尽量避免使用那些多变的具体实现。对象创建的过程也应该收到严格的限制,作者推荐使用抽象工厂模式去创建对象
      • 保持架构整洁-读《架构整洁之道》
    • 不要在具体类上创建衍生类:继承的以依赖关系是最重的,最难修改的,应该避免继承具体类
    • 不要重写包含具体实现的函数:即使我们重写了函数,也无法消除原函数已经存在的一些依赖。这条可以看成是上一条的衍生
    • 应该避免在代码中写入任何与具体实现相关的名字(就是 DIP 的另一种说法而已)
  • DIP 中的接口是调用者维护而不是实现者维护

第12章:组件

  • 组件:组件是软件的部署单元(类似于 Bundle),是整个软件部署过程中可以独立完成部署的最小实体。无论采用哪种部署方式,组件都可以被独立部署也就意味着它可以被独立开发
  • 组件化的插件架构:组件化是插件架构的基础
  • 一些名词:墨菲定律:坏事情总会发生

第13章:组件聚合

fk 这章有点玄学

  • **REP:复用/发布等同原则:**软件复用的最小粒度应该等于它的发布粒度
    • 一个组件中的类与模块必须是相互关联的,他们应该有一个共同的大主题或者方向
  • **CCP:共同闭包原则:**我们应该将那些会同时修改,并且为相同目的修改的类放到同一个组件中。
    • 这是 SRP 原则在组件层面的一个阐述。SRP:一个类应该只有一个被修改的原因;CCP:一个组件不应该同时存在多个变更原因
    • SRP是说将变更原因不同的函数放到不同的类中,CCP是说将变更原因不同的类放到不同的组件中。这俩法则总结起来就是:要将相同原因修改,并且需要同时修改的东西放在一起
    • 好处:有效降低组件发布、部署、验证所带来的工作量
  • **CRP:共同复用原则:**不要强迫一个组件的用户依赖他们不需要的东西。
    • 这是 ISP 的组件版本,两者都强调:不要依赖不需要用到的东西。ISP:不依赖带有不需要函数的类;CRP:不依赖带有不需要类的组件
  • 上述三种原则是互相矛盾的,REP、CCP强调聚合,CRP强调排他。架构师需要在研发性与复用性上进行取舍,根据程序的需求进行平衡
保持架构整洁-读《架构整洁之道》

第14章:组件耦合

本章主要关注组件之间的关系

  • 无依赖环原则:ADD:组建以来关系图中不能有环。即不能有循环依赖
    • 打破循环依赖的集中方式:1. 依赖反转 DIP 2. 创建一个新的组件将循环依赖的地方放到新组件中,原有组件共同依赖新组件
保持架构整洁-读《架构整洁之道》
  • 抖动:我们必须持续地监控项目中的循环依赖关系,动态消除循环依赖
  • 避免假大空的设计:组件结构图无法做到自上而下的设计,它是随着软件系统的变化而变化的。它是一张应用程序在构建性与维护性方面的一张地图
  • 稳定依赖原则(SDP):依赖关系必须朝向更稳定的方向
    • 稳定性:稳定是指很难移动,所以稳定与变更所需要的工作量有关系。软件组件的变更难度与很多因素有关,比如复杂度、清晰度、代码量等,但是一个更直观的因素是被依赖的次数
    • 稳定性指标:I(不稳定性) = Fan-out / (Fan-in + Fan-out) I = 1 时是最不稳定的。这里说的不稳定是指改动的成本最小因为他不依赖别人
      • Fan-in:入向依赖(被依赖)
      • Fan-out:出向依赖(依赖别人)
    • 稳定依赖原则要求组件的 I 指标按照依赖方向逐渐递减
  • 稳定抽象原则(SAP):一个组件的抽象化程度应该与其稳定性保持一致
    • SAP 为组件的稳定性与抽象化程度建立了一种关联关系:1. 稳定的组件应该是抽象的(简单理解就是被很多人引用的组件应该是抽象的这样方便扩展) 2. 不稳定的组件应该包含具体的实现代码
    • 抽象化指标:A(抽象化程度) = Na(组件中抽象类与接口的数量) / Nc(组件中类的数量)。A = 0 代表组件中没有抽象类,A= 1 代表组件中全是抽象类
  • **衡量组件质量:**基于 SDP 与 SAP 可以通过下面的坐标系来衡量组件的质量,正常的组件质量应该在主序列线附近才行
    • 痛苦区:被别人大量依赖但是全是细节代码,很少有抽象就导致系统的变更很费劲
    • 无用区:包含了大量的抽象代码但是没有人引用
保持架构整洁-读《架构整洁之道》

第15章:什么是软件架构

  • **软件架构的终极目标:**最大化程序员的生产力,同时最小化系统的总运营成本。即:全局最优解
    • 要坚定地在一线写代码,架构师应该是解决问题最强的那个人而不是高高在上指点江山的人(不贴合业务也设计不出啥好架构)
  • 好架构的一些具体表现:
    • 方便开发:一个开发起来很困难的软件系统一般不可能有一个长久、健康的生命周期
    • 方便部署:可以实现系统的一键式轻松部署
    • 揭示系统运行:好的架构不会对系统的运行起多大的影响(运行时的好性能大概率还得看硬件或者其他的一些方案),但是好的架构可以使开发人员对系统的运行过程一目了然
    • 降低维护成本:系统的维护成本体现在“探秘”(确定修改位置)、“风险”(确定影响范围),好的架构可以大幅降低维护这两个的维护成本
    • 保留可选项:软件系统可以降解成 “策略”(核心业务逻辑)、“细节”(核心业务逻辑之外的细节实践,比如框架、UI等)两个方面。好的架构可以让细节与策略分离,并且尽可能推迟细节的实施时间。一个好的架构师应该致力于最大化可选项的数量
      • 简单理解就是先实现核心的业务逻辑,最后具体使用 React 框架还是 Vue 框架可以往后推迟,因为核心业务逻辑与框架是解耦的

第16章:独立性

  • 一个设计良好的架构必须支持以下几点:
    • 系统的用例与维护:(见名知意)——设计良好的架构在行为上对系统最重要的作用是明确和显示地反应系统设计意图的行为,使其在架构层面上可见,这样新的开发者去维护系统时可以快速上手,降低探秘、风险成本
    • 正常运行:在运行上也应该贴合业务,比如分布式的业务在架构设计上就不应该是单体架构
    • 系统的开发:多个不同团队并行开发时需要良好的架构隔离,方便他们独立完成工作互不干扰
    • 系统的部署:好的架构可以实现一键式部署,不需要额外繁杂的脚本与工程设置
  • 保留可选项(核心业务逻辑需要屏蔽底层细节):好的架构应该通过保留可选项的方式,让系统在任何情况下都能方便地做出变更
  • **一些解耦方式:**只要系统经过了下述“水平”、“垂直”两个维度的解耦,那不同的软件开发团队就可以并行工作互不影响了
    • 按层解耦(水平解耦):通过SRP(单一职责)与CCP(共同闭包)以及既定的系统设计意图来隔离那些变更原因不同的部分,合并变更原因相同的部分。这样系统就会出现水平的很多分层:UI、用例、实体等
    • 用例解耦(垂直解耦):每个用例都会用到一些UI、数据库等功能,因此我们在实现水平解耦的同时也应该按照垂直方向对用例分割成多个垂直切片
    • 总结:如果我们按照变更原因的不同对系统进行水平解耦,那就可以在业务变更的时候持续地向系统中添加新的用例而不影响原有用例。如果再加上垂直的用例解耦,那么新增用例时用到的就是属于自己的那部分 UI、数据库等功能,这样就更不容易影响原来的用例了
  • “重复”不一定都是错的:一些伪重复(有这不同的变更缘由与变更速度,比如某些服务于特定组件的工具类)并不是有害的,强行把这些伪重复干掉后反而会造成两个模块的耦合
  • 延伸阅读:
    • 康威定律——任何一个组织在设计系统时,往往都会复制出一个与该组织内沟通结构相同的系统
    • SOA:service-oriented architecture 面相服务的架构,指的就是一种架构的解耦模式

第17章:划分边界

  • 软件架构的本身就是划分边界的艺术,边界的作用就是将软件分割成各种元素以便约束两侧之间的依赖关系
  • 不要提前设计各种假大空且未来不一定用到的架构,那玩意就是在过度设计并且可能没啥用
  • 架构图上的边界线例子:(接口位于高层次的模块内,依赖方向是底层依赖高层,高层不依赖底层)
保持架构整洁-读《架构整洁之道》

第18章:边界剖析

系统架构中最强的边界就是“服务”,一个系统中通常会包含高通信量、低延迟的单体架构与低通信量、高延迟的服务架构

第19章:策略与层次

  • 策略:核心的业务逻辑;层次:外围细节,距离输入/输出越远,层次越高(所以核心业务逻辑是层次是最高的)
  • 软件的本质:一组仔细描述如何将输入转化为输出的高级策略语句集合;软件架构的工作重点之一就是将这些策略进行分组并重新排列成一个有向无环图
  • 系统中的高层策略一般变动比较小,所以
    • 让源码的依赖方向都指向高层策略,可以大幅降低底层策略改动对系统的影响
    • 底层策略本质上应该成为高层策略的的插件,高层策略不感知底层策略

第20章:业务逻辑

  • 业务实体(Entity):关键业务逻辑与关键业务数据的组合,这俩货是紧密相关的,很适合放在同一个类中。当然业务实体不一定非得用类来表达。(笔者注:不过一般都用类)
    • 关键业务逻辑:业务逻辑就是系统中用于挣钱或者省钱的逻辑或过程,无论是否在计算机上实现,这些业务逻辑都是不变的。业务逻辑应该保持纯粹,与细节无关
      • 比如银行贷款系统中用户贷款人信贷评估就是业务逻辑。即使不用计算机,银行的柜员也得自己评估
    • 关键业务数据:关键业务逻辑通常要处理一些数据,比如贷款系统中的贷款量、利率等
  • 用例(Use Case):某种特定应用场景下的业务逻辑,控制着实体之间的交互以实现某个业务功能。它定义了输入、输出过程中的所有步骤。
    • 例子:银行放贷系统中,贷款的评估过程需要经过名称输入、调取信用记录、贷款量评估、输出可贷量等步骤,这些步骤就是一个用例。它会调用业务实体去实现这个场景下的业务功能。同时与具体的框架等细节没有鸡毛关系~~~,这货是脱离细节可测试的
  • 不要把业务实体当成数据结构!!!虽然业务实体与架构边界交互过程中的数据结构有很大一部分重复,但是这两个东西并不一样。如果强行合并会导致各种数据转换,导致熵增

第21章:尖叫的软件架构

  • 架构必须要解决:在哪里找代码、怎么改代码的问题。让新人在不依赖老人与文档的前提下自己快速上手
  • 架构的核心目标:只关注用例,为用例服务,隔绝其他细节
  • 框架是工具不是生活信条,软件的核心是业务逻辑而不是框架细节
  • 架构应该是可脱离细节被测试

第22章:整洁架构(灵魂!!!!!!)

保持架构整洁-读《架构整洁之道》
  • 良好架构本质上是实现了“关注点分离”,基于良好架构设计出来的软件系统普遍存在以下特点:
    • 独立于框架
    • 可被测试
    • 独立于 UI
    • 独立于数据库
    • 独立于任何外部机构:不需要感知到任何外部的接口或服务
  • 依赖关系法则:依赖关系都是由外部圆指向内部圆,即低层次的细节依赖高层次的策略
  • 关于四层架构的名词解释:
    • 业务实体:整个系统中关键的业务逻辑。既可以是一个带有方法的对象(面相对象),也可以是一组数据结构与函数的集合(非面向对象)
    • 用例层:特定应用场景下的业务逻辑,封装了整个系统的用例。这些用例引导数据在实体间流入流出,对业务实体(通用逻辑)进行组合以完成特定业务逻辑
    • 接口适配器:通常是一组数据转换器进行数据转化,MVC框架 GUI框架都应该在这一层,如果使用了 SQL 数据库,对 SQL 语句的封装也在这里
      • 展示器:用于接收数据并转化成视图的数据模型,比如把数据格式化后交给 React 的 state 对象并触发 render
      • 控制器:业务流程开始的起点,比如接收 View 的一些事件,并把数据转化好后调用用例层去实现业务响应。或者接收一些外部服务的调用开始业务流程
      • 数据库网关:一个多态接口,包含了应用程序在数据库上要执行的增删改查等操作。比如要查询昨天哪些用户登录了系统,这个多态接口就应该具备 public User getUserByDate(Date data);
    • 框架与驱动程序:工具、数据库等细节,这一层只需要编写一些与内层沟通的黏合性代码就可以了
      • 注意:像 Hibernate GreenDao等 ORM 框架应该属于数据库层
  • 图中右下角的数据流向:
    • 控制器调用用例层拿到结果后传递给展示器。展示器进行数据转化后交给视图
  • 跨越边界的数据结构必须是简单的,也不应该取巧去依赖内层的 Entity 造成内层依赖外层

注意:软件架构不一定只包含四层,这里仅说明思想

第23章:展示器与谦卑对象

  • 谦卑对象模式:用于区分那些容易测试与难以测试的行为,并将这两种行为隔离。难以测试的一组叫谦卑组,这些组里面的行为已经简化到不能再简化了
    • 谦卑:知道自己是难以测试的,是局限的。所以只发挥自己桥梁与传递的作用,不进行逻辑干预。比如 Redux 中的 state 会映射到 View 上,state 就是谦卑对象
  • 展示器与视图
    • 展示器(不是谦卑的,是可测试的):从应用程序接收数据并把数据格式化后交给视图。比如 UI 上要展示日期,展示器的作用就是将 Date 对象转化成 String 对象并填充到视图的数据模型中
    • 视图(谦卑的,难以测试的):一般是 View 的数据模型,比如 React 中的 state 对象

第24章:不完全边界

构建完整边界是一个非常耗费资源的事情,如果完全按照规范来搞,那我们就需要为边界设计双向的多态接口、所有的依赖关系、用于输入和输出的数据结构,这会造成项目前期根本没有时间去做这些东西。不完全边通过只实现部分边界既解放了生产力又遵循了边界规范。下面是几种不完全边界的实现方式:

  • 省去最后一步:拆分组件后(各组件可以互相独立玩,已经定义了组件边界、依赖关系等)不做服务化的拆分,仍然将这些组件打包成一个组件。这样省了维护各个组件版本号的工作量
  • 单向边界:我理解就是只实现高层策略通过DIP以来底层细节的边界,底层细节直接依赖高层策略
  • Facade模式:连反转的工作都省了,直接在同一个 Facade 中维护所有的调用关系。这个的缺点是调用方会传递性依赖 Facade 内所有的映射关系

第25章:层次与边界

通过一个文本游戏的例子说明了再简单的应用程序为了保护其后续的扩展也应该有分层与边界(当然得结合实际情况,万年不动的代码就没必要了)

  • 高层的策略调用底层的细节时要通过抽象去调用(注意:这些抽象的接口或者 API 是在高层策略的组件中的)
  • YAGNI原则:you arent't going to need it,不要过度设计,过度设计比没有设计更沙雕。(PS:所有的设计都要结合业务的实际情况与产品的发展方向)

第26章:Main 组件

Main 是最细节的地方,负责创建、协调、组装组件(比如高层使用的抽象,就需要在 Main 中把抽象类赋予实现类)。Main 应该作为应用程序的一个插件,不同的环境依照配置的不同也可以有多个 Main 组件

第27章:服务:宏观与微观

现在流行的微服务、SOA(基于服务的架构)只是应用程序的一种分割方式,这玩意并不能代表架构~

  • 面向服务的架构有一些优势悖论
    • 解耦合优势:如果两个微服务共同依赖了某些共享数据,这两个微服务就强耦合了,那他们就不存在解耦优势
    • 独立开发部署优势:如果服务之间有数据耦合,一样无法独立部署(比如 A 服务处理完的数据 B 才能处理)。无数历史证明,大型的系统一样可以采用单体架构
  • 所有大型项目中都会遇到的一个问题:横跨型变更:迭代过程中所有组件都要改动
  • 服务边界并不代表系统架构边界,服务内部的组件边界才是(基于服务的架构只是一种架构分割形式),为了解决横跨型变更,服务内部的组件边界必须按照依赖指向原则

第28章:测试边界

测试代码也是应用程序的一部分

  • 测试代码是整洁架构圈中最外层的部分,非常独立
  • 不要尝试去测试 UI,软件设计第一原则就是不要依赖多变的东西。基于 GUI 的测试注定是脆弱的(fragile tests problem)。多基于逻辑的测试。
  • 不要将测试代码与应用代码耦合(曹。。。 这玩意可困难。。。。。)

第29章:整洁嵌入式架构

主要说明了在嵌入式开发中,整洁架构的分层也是非常有必要的

  • Doug 大佬的一些观点:虽然软件本身不会随着时间的推移而磨损,但是硬件及其固件会随着时间推移而过时,随即也需要对软件做相应的改动
  • 基于上述观点,作者对嵌入式的分层进行了一些抽象
保持架构整洁-读《架构整洁之道》
  • 硬件:hardware
  • 固件:我理解就是驱动程序,与具体的硬件耦合。比如 Android 工程师没有将业务逻辑与 Android 的 api 调用分开,那实际上就是在写固件代码
  • HAL:硬件抽象层,屏蔽各种硬件细节,让软件不感知具体硬件
  • OSAL:操作系统抽象层
  • 软件:适配多种硬件的应用程序
  • Kent Beck 描述的软件构建过程中的三个阶段:
    • 先让代码跑起来(先跑起来再说)
    • 然后试图将他变好(基于重构,让自己与他人能更好的理解代码,即代码可以自表达)
    • 最后试着让他变快(按照性能要求重构代码满足性能提升)

第30章:实现细节

  • 数据库是实现细节
  • Web 是实现细节
  • 应用程序框架是实现细节,框架不等于业务架构!

第31章:拾遗

讲了一些封装方式,核心有下面这几点:

  • 逻辑的调用只应该调用相邻层的代码,即所有层的调用都不应该绕过业务逻辑层(我理解就是:usecase层)
  • 架构设计好了以后后续的实施保障及维护也很重要,防止架构腐化,作者给的建议是依赖编译器(比如 java 的 protect 修饰符将对外的暴露限制在包内部,防止跨层调用)

一些延伸阅读

  • 《Object Oriented Software Engineering》—— Ivar Jacobson:业务用例驱动的设计方式

欢迎大家微信扫下面二维码,关注我的公众号【趣code】,一起成长~

保持架构整洁-读《架构整洁之道》