:::info
@Arno(surfacew) 学习演进式架构,笔记与思考。
Book:https://book.douban.com/subject/34793521/
:::
软件架构的关注要素非常之多,随便一列都是一大堆,演进能力,也可以为其中的一种。
演进式的架构意在保证在一切都在变化的情况下,软件的架构设计可以长期适应演变过程,类似于生物的适应性进化系统(DNA 的演变系统)。意义对软件企业来说在于成本控制,是经济友好的。
软件工程,特别是在编程语言、工具、DevOPS 等影响下,已经将自动化的思想带入,极大程度上降低了软件变更背后的成本。在完成软件架构的设计之后,防止架构退化或者代码腐化也是一门命题。因此演进能力其实是一种持续架构的能力。
定义
作者将演进式的架构定义为:
:::success 演进式架构支持跨多个维度的引导性增量变更。 :::
后续的行文将来逐一介绍这个概念的定义(感觉比较抽象拗口是吧?)。
- 增量变更:增量变更描述了构建软件架构的两个方面,即:增量的构建和软件部署。
- 引导性变更:一旦架构师选择了重要的架构特征,他们就会把变更引导进入架构,以保护这些重要的特征。作者引入了一种「适应性函数」的概念,用于计算潜在的解决方案和既定目标的差距。
- 多个架构维度:架构师分析架构设计考虑的维度,比如:框架、依赖库、实现语言、数据设计、安全、需求、运维(部署云环境)、组织(康威定律)等等,每个视角可以构成架构的维度,在这些维度上再去关注软件系统要解决的问题,比如伸缩性、分布式、事务等等。
作者聊到了康威在论文里提到的:每当新的团队组建,其它团队就会缩小职责范围,能够有效执行的可选方案也会随之变小。换言之就是「人们很难改变其职责范围之外的事情」,软件架构师便需要时刻关注团队的分工模式,以让架构目标和团队结构保持一致。
适应度函数
现实中,适应度函数由很多不同维度组成。包括:性能、可靠性、安全性、卡操作性、代码规范、集成度 … 希望通过适应度函数来表示每一项架构需求。而系统全局适度函数可以表示为一系列子函数的集合函数 。
其中 x_i 是架构的选型(设计因子,比如选用基于事件通信的架构,或者选用 C++ 作为编程语言等等),R_i 是具体关注要素的适合度(比如:健壮性、可伸缩性、性能等等)
适度函数分为原子和整体,可以分为:
- 触发式与持续式
- 静态与动态
- 自动适应和手动适应
- 临时适度函数
- 针对领域的特定适度函数
尽早确定适度函数,可以方便对架构的评估和演进,对适度函数进行评审也类似地在做架构评审。
实施增量变更
DevOps 过程。
一定要明白,软件架构设计是一个动态寻求解决方案的过程,技术变化非常之快,可能 3 年前的架构和 3 年后的架构迥然不同。
所以,以动态和增量,以及发展的眼光看待软件架构的设计,在变中寻求不变是一种难得可贵的能力。
- 关注可测试性(TDD 模式)
- 关注构建部署流水线
- 设计适应函数做架构评估(演变评估)
架构耦合
- 模块化,技术层面的模块化是解决编程的复用问题,也意味着逻辑分离和低耦合。
- 作者引入架构量子(最小单元)的概念,用于描述架构中不能够分解的最小单元(可以独立部署的组件),它包含了支持正常系统工作的所有结构性元素。
- 作者认为,无论在任何架构设计中,架构师都应该关注并显式定义架构量子的大小,因为它决定了架构中进行增量变更的可能性。这些子元素之间的互相耦合好比自然界中四种力,耦合关系需要进行精心设计,比如事务就是强耦合的设计好比强力,事件更偏向于松散耦合好比弱力。
后续作者也依次介绍了主流架构的耦合和适度函数的分析:
- 大泥团架构:超级耦合,反面案例,无结构可言,适度函数设计困难,增量变更实施困难
- 单体架构(巨石架构)
- 非结构化的:组件之间耦合度较高
- 分层的:比如经典的展现层、业务逻辑层、持久层分离等等,适度降低组件之间的耦合度,关注点分离
- 模块化的:模块化的设计在耦合关系上做进一步精细化设计,在复用性、可替换性上最更深层次的关注,实现高内聚低耦合,是单体架构下比较好的实现范式
- 微内核架构:在模块化的基础上,将核心和扩展(插件)分离,去控制复杂性,是典型的类似于操作系统(OS)单体架构设计的经典,但强依赖内核的模式,导致其插件系统在跨项目上很难得到复用
- 事件驱动架构:虽然松耦合,但是异步系统带来的认知成本和测试调试难度极高
- 代理模式
- 中介模式
- 服务导向架构
- 企业服务总线驱动架构(SOA):非常方法论和固化的一种模式,实施下来比较「奢侈」,形式化系统比较厉害,有服务管理(发现、路由)、服务治理、服务编排等,形式化支持,但是灵活度和可实施性上对开发者约束比较多,导致 架构量子往往比较大,灵活度偏低,尤其是技术和业务灵活度低。
- ESB 机制的模式,其实不是为了系统的独立演进而设计,因此在应对耦合程度和增量变更上没有太多收益
- 微服务架构(MS):当今主流架构模式,往往针对比较复杂的大型系统做设计,符合康威定律包括 DDD 等对当代大型组织协同的模式。
- 得益于服务实现的高度自由,增量变更维度由服务提供商自理,因此 DevOps 服务可以独立实践
- 服务之间的边界设立,也可以降低耦合,服务发现和微服务治理可以弱化这些复杂度
- DDD 对服务的界限上下文(BoundingContext)的定义,也就自然是对架构量子的一种描述
- 企业服务总线驱动架构(SOA):非常方法论和固化的一种模式,实施下来比较「奢侈」,形式化系统比较厉害,有服务管理(发现、路由)、服务治理、服务编排等,形式化支持,但是灵活度和可实施性上对开发者约束比较多,导致 架构量子往往比较大,灵活度偏低,尤其是技术和业务灵活度低。
- 无服务架构(Serverless)
- 无服务架构目前依旧无法适用于复杂业务,这里暂不做讨论,企业级业务几乎无法基于无服务架构得以实现,还有一段路要走
演进式数据
- DBA 和开发人员需要严格地测试数据库模式的变更,同时数据模式和代码也应该做统一的版本管理,源代码和数据库模式是共生的。数据的增量也应该和代码的增量一样,提供自动化的迁移工具而非手动操作数据变更。
- 杜绝不恰当的数据耦合
构建可演进的架构
关注架构创建到迁移的关注点。
- 识别受影响的架构维度,比如:性能、安全、分布式 …
- 为每个维度定义适应度函数
- 使用部署流水线自动化适应度函数
改良现有架构的思路:
- 适当的耦合和内聚,设计架构之中的「架构量子」(颗粒度控制)
- 工程实践上关注自动化,提升效率
- 用好适应度函数,评估架构和选型打来的技术变更适应度
- 注意使用商用成品软件(COTS)来带的陷阱(主要是代码黑盒)
架构迁移的思路:
- 关注边界的划分很重要,业务功能的分组、事务边界、部署计划等等都需要关注
- 关注演进过程之中,模块之间的交互问题和依赖问题
演进式架构的指南:
- 去除不必要的可变性:比如通过不可变的基础设施来代替雪花服务器(手动运维的服务器)
- 让决策可逆:灰度开关、回滚等机制
- 演进优于预测:架构师很难对未知的问题做预测,从已知问题开始演进
- 构建防腐层:将系统对外依赖(商业 or 开源技术)的部分做抽象,而不依赖具体的技术提供方
- 构建可牺牲的架构:为清除技术债,要勇敢地清理那些以往产生的设计妥协
- 对应外部变化:慎重引入外部依赖(带来复杂度和不可控),最好以「拉取」的方式来做依赖管理,若出现问题不应继续持续集成的流程
- 慎重更新库和框架:按需更新,而不是盲目追求所谓的「新潮技术」
- 持续交付优于快照:DevOps 软件工程 YYDS!
- 服务内部版本化:抹平破坏性变更
陷阱和反模式
技术架构:
- 反模式:供应商为王,一种完全围绕供应商产品去架构
- 陷阱:抽象漏洞,了解复杂技术栈脆弱的部分,可以尝试用适应度函数来保护它们,或者硬生生地下一层抽象的细节吃透,否则遇到问题很难排查
- 反模式:最后 10% 的陷阱,在抽象的另一端存在一种复用陷阱,它往往隐藏在套装软件、平台和框架中,比如部分低代码平台,在客户最后 20% 和 10% 复杂功能所花费的成本远超过本身使用的成本,甚至还无法实现,这种成为 10% 的陷阱。
- 反模式:代码复用的滥用,为了复用而复用是蠢事情
- 陷阱:简历驱动开发(这里我想补充一个叫做 KPI 驱动开发 … 😅),千万不要为了架构而构建架构,架构是为了解决具体的问题,是一种平衡和 TradeOff
增量模式:
- 反模式:管理不当,管理模式和组织的设计影响架构的设计,老套的管理模式会影响架构的实施
- 陷阱:发布过慢,演进式的架构适合高频变更和增量更新的架构,不适合专家系统和专家过程(比如科研和国防等领域)
业务问题:
- 陷阱:产品定制,定制往往带来较高的维护成本,无论是多分支、功能开关还是编排,无疑大幅增加了研发和测试交付流程的复杂度
- 反模式:报表,很多公司把报表和数据库层直接耦合,这类模式让后续数据变更寸步难行
- 陷阱:规划视野,不断更新视野,不要做井底之蛙
实践演进式架构
- 全功能产品研发团队:PD、架构师、测试、研发、DBA 等多元角色组成
- 围绕业务能力组建团队:分离团队职责和关注点,架构师做上层设计穿针引线
- 产品高于项目:围绕产品组建团队,而非项目组件,产品组件的团队利于业务层的持续思考和演进,项目就变外包了
- 应对外部变化:团队和外部关系尽量是一种高度解耦的模式。
- 减少团队成员的连接数:复杂度是
- 重视团队文化:比如工程师文化,或者极客文化
- 寻找架构量子和成本的平衡点:粒度的分解确乎和经济效应有关,需要 TradeOff