:::info @Arno(surfacew) 学习演进式架构,笔记与思考。
Book:https://book.douban.com/subject/34793521/
image.png :::

软件架构的关注要素非常之多,随便一列都是一大堆,演进能力,也可以为其中的一种

演进式的架构意在保证在一切都在变化的情况下,软件的架构设计可以长期适应演变过程,类似于生物的适应性进化系统(DNA 的演变系统)。意义对软件企业来说在于成本控制,是经济友好的。

软件工程,特别是在编程语言、工具、DevOPS 等影响下,已经将自动化的思想带入,极大程度上降低了软件变更背后的成本。在完成软件架构的设计之后,防止架构退化或者代码腐化也是一门命题。因此演进能力其实是一种持续架构的能力。

定义

作者将演进式的架构定义为:

:::success 演进式架构支持跨多个维度引导性增量变更。 :::

后续的行文将来逐一介绍这个概念的定义(感觉比较抽象拗口是吧?)。

  • 增量变更:增量变更描述了构建软件架构的两个方面,即:增量的构建和软件部署。
  • 引导性变更:一旦架构师选择了重要的架构特征,他们就会把变更引导进入架构,以保护这些重要的特征。作者引入了一种「适应性函数」的概念,用于计算潜在的解决方案和既定目标的差距。
  • 多个架构维度:架构师分析架构设计考虑的维度,比如:框架、依赖库、实现语言、数据设计、安全、需求、运维(部署云环境)、组织(康威定律)等等,每个视角可以构成架构的维度,在这些维度上再去关注软件系统要解决的问题,比如伸缩性、分布式、事务等等。

作者聊到了康威在论文里提到的:每当新的团队组建,其它团队就会缩小职责范围,能够有效执行的可选方案也会随之变小。换言之就是「人们很难改变其职责范围之外的事情」,软件架构师便需要时刻关注团队的分工模式,以让架构目标和团队结构保持一致。

适应度函数

现实中,适应度函数由很多不同维度组成。包括:性能、可靠性、安全性、卡操作性、代码规范、集成度 … 希望通过适应度函数来表示每一项架构需求。而系统全局适度函数可以表示为一系列子函数的集合函数 《演进式架构》 - 图2

《演进式架构》 - 图3

其中 x_i 是架构的选型(设计因子,比如选用基于事件通信的架构,或者选用 C++ 作为编程语言等等),R_i 是具体关注要素的适合度(比如:健壮性、可伸缩性、性能等等)

适度函数分为原子和整体,可以分为:

  • 触发式与持续式
  • 静态与动态
  • 自动适应和手动适应
  • 临时适度函数
  • 针对领域的特定适度函数

尽早确定适度函数,可以方便对架构的评估和演进,对适度函数进行评审也类似地在做架构评审。

实施增量变更

DevOps 过程。

一定要明白,软件架构设计是一个动态寻求解决方案的过程,技术变化非常之快,可能 3 年前的架构和 3 年后的架构迥然不同。

所以,以动态和增量,以及发展的眼光看待软件架构的设计,在变中寻求不变是一种难得可贵的能力

  • 关注可测试性(TDD 模式)
  • 关注构建部署流水线
  • 设计适应函数做架构评估(演变评估)

架构耦合

  • 模块化,技术层面的模块化是解决编程的复用问题,也意味着逻辑分离和低耦合。
  • 作者引入架构量子(最小单元)的概念,用于描述架构中不能够分解的最小单元(可以独立部署的组件),它包含了支持正常系统工作的所有结构性元素。
  • 作者认为,无论在任何架构设计中,架构师都应该关注并显式定义架构量子的大小,因为它决定了架构中进行增量变更的可能性。这些子元素之间的互相耦合好比自然界中四种力,耦合关系需要进行精心设计,比如事务就是强耦合的设计好比强力,事件更偏向于松散耦合好比弱力。

后续作者也依次介绍了主流架构的耦合和适度函数的分析:

  • 大泥团架构:超级耦合,反面案例,无结构可言,适度函数设计困难,增量变更实施困难
  • 单体架构(巨石架构)
    • 非结构化的:组件之间耦合度较高
    • 分层的:比如经典的展现层、业务逻辑层、持久层分离等等,适度降低组件之间的耦合度,关注点分离
    • 模块化的:模块化的设计在耦合关系上做进一步精细化设计,在复用性、可替换性上最更深层次的关注,实现高内聚低耦合,是单体架构下比较好的实现范式
    • 微内核架构:在模块化的基础上,将核心和扩展(插件)分离,去控制复杂性,是典型的类似于操作系统(OS)单体架构设计的经典,但强依赖内核的模式,导致其插件系统在跨项目上很难得到复用
  • 事件驱动架构:虽然松耦合,但是异步系统带来的认知成本和测试调试难度极高
    • 代理模式
    • 中介模式
  • 服务导向架构
    • 企业服务总线驱动架构(SOA):非常方法论和固化的一种模式,实施下来比较「奢侈」,形式化系统比较厉害,有服务管理(发现、路由)、服务治理、服务编排等,形式化支持,但是灵活度和可实施性上对开发者约束比较多,导致 架构量子往往比较大,灵活度偏低,尤其是技术和业务灵活度低。
      • ESB 机制的模式,其实不是为了系统的独立演进而设计,因此在应对耦合程度和增量变更上没有太多收益
    • 微服务架构(MS):当今主流架构模式,往往针对比较复杂的大型系统做设计,符合康威定律包括 DDD 等对当代大型组织协同的模式。
      • 得益于服务实现的高度自由,增量变更维度由服务提供商自理,因此 DevOps 服务可以独立实践
      • 服务之间的边界设立,也可以降低耦合,服务发现和微服务治理可以弱化这些复杂度
      • DDD 对服务的界限上下文(BoundingContext)的定义,也就自然是对架构量子的一种描述
  • 无服务架构(Serverless)
    • 无服务架构目前依旧无法适用于复杂业务,这里暂不做讨论,企业级业务几乎无法基于无服务架构得以实现,还有一段路要走

演进式数据

  • DBA 和开发人员需要严格地测试数据库模式的变更,同时数据模式和代码也应该做统一的版本管理,源代码和数据库模式是共生的。数据的增量也应该和代码的增量一样,提供自动化的迁移工具而非手动操作数据变更。
  • 杜绝不恰当的数据耦合

构建可演进的架构

关注架构创建到迁移的关注点。

  • 识别受影响的架构维度,比如:性能、安全、分布式 …
  • 为每个维度定义适应度函数
  • 使用部署流水线自动化适应度函数

改良现有架构的思路:

  • 适当的耦合和内聚,设计架构之中的「架构量子」(颗粒度控制)
  • 工程实践上关注自动化,提升效率
  • 用好适应度函数,评估架构和选型打来的技术变更适应度
  • 注意使用商用成品软件(COTS)来带的陷阱(主要是代码黑盒)

架构迁移的思路:

  • 关注边界的划分很重要,业务功能的分组、事务边界、部署计划等等都需要关注
  • 关注演进过程之中,模块之间的交互问题和依赖问题

演进式架构的指南:

  • 去除不必要的可变性:比如通过不可变的基础设施来代替雪花服务器(手动运维的服务器)
  • 让决策可逆:灰度开关、回滚等机制
  • 演进优于预测:架构师很难对未知的问题做预测,从已知问题开始演进
  • 构建防腐层:将系统对外依赖(商业 or 开源技术)的部分做抽象,而不依赖具体的技术提供方
  • 构建可牺牲的架构:为清除技术债,要勇敢地清理那些以往产生的设计妥协
  • 对应外部变化:慎重引入外部依赖(带来复杂度和不可控),最好以「拉取」的方式来做依赖管理,若出现问题不应继续持续集成的流程
  • 慎重更新库和框架:按需更新,而不是盲目追求所谓的「新潮技术」
  • 持续交付优于快照:DevOps 软件工程 YYDS!
  • 服务内部版本化:抹平破坏性变更

陷阱和反模式

技术架构:

  • 反模式:供应商为王,一种完全围绕供应商产品去架构
  • 陷阱:抽象漏洞,了解复杂技术栈脆弱的部分,可以尝试用适应度函数来保护它们,或者硬生生地下一层抽象的细节吃透,否则遇到问题很难排查
  • 反模式:最后 10% 的陷阱,在抽象的另一端存在一种复用陷阱,它往往隐藏在套装软件、平台和框架中,比如部分低代码平台,在客户最后 20% 和 10% 复杂功能所花费的成本远超过本身使用的成本,甚至还无法实现,这种成为 10% 的陷阱。
  • 反模式:代码复用的滥用,为了复用而复用是蠢事情
  • 陷阱:简历驱动开发(这里我想补充一个叫做 KPI 驱动开发 … 😅),千万不要为了架构而构建架构,架构是为了解决具体的问题,是一种平衡和 TradeOff

增量模式:

  • 反模式:管理不当,管理模式和组织的设计影响架构的设计,老套的管理模式会影响架构的实施
  • 陷阱:发布过慢,演进式的架构适合高频变更和增量更新的架构,不适合专家系统和专家过程(比如科研和国防等领域)

业务问题:

  • 陷阱:产品定制,定制往往带来较高的维护成本,无论是多分支、功能开关还是编排,无疑大幅增加了研发和测试交付流程的复杂度
  • 反模式:报表,很多公司把报表和数据库层直接耦合,这类模式让后续数据变更寸步难行
  • 陷阱:规划视野,不断更新视野,不要做井底之蛙

实践演进式架构

  • 全功能产品研发团队:PD、架构师、测试、研发、DBA 等多元角色组成
  • 围绕业务能力组建团队:分离团队职责和关注点,架构师做上层设计穿针引线
  • 产品高于项目:围绕产品组建团队,而非项目组件,产品组件的团队利于业务层的持续思考和演进,项目就变外包了
  • 应对外部变化:团队和外部关系尽量是一种高度解耦的模式。
  • 减少团队成员的连接数:复杂度是 《演进式架构》 - 图4
  • 重视团队文化:比如工程师文化,或者极客文化
  • 寻找架构量子和成本的平衡点:粒度的分解确乎和经济效应有关,需要 TradeOff