演进式架构
适应度函数
适应度函数是一种限制模型。它决定了架构应该拥有或者不应该拥有哪些能力
架构的演进是需要加以限制的演进 否则就变成了无序生长
分类
原子与整体
原子用来验证某一维度 整体用来验证多个维度甚至于整个系统
触发式与持续式
触发式基于某些事件来验证 持续式则如名字那样 使用监控驱动开发(MDD)来监控生产系统指标从而评估系统的健康程度
静态与动态
静态函数得到的值是固定的 动态的则是会依据上下文在一定区间内浮动
自动与手动
处于技术或者非技术原因 某些验证无法自动化
维度
- 关键:影响架构决策的 (系统所需的特性)
- 相关:不会影响架构决策的 (代码质量...)
- 不相关:与架构无关 (交付时间...)
审查
及时审视 确定是否需要新函数、现有的函数会不会过大、是否有更好的方法验证与测量
增量变更
在部署流水线中的适应度函数
通过在部署流水线中加的函数 不仅能客观量化结果 也有一致的执行机制
假设驱动开发
- 使用科学的方式度量 基于数据来假设用户的需求而非真正收集用户需求
架构模式与演进能力
架构模块间的耦合越小 演进能力越强
数据演进
演进式数据库设计
- 严格限制模式变更
- 版本化(代码跟模式需要做版本管理)
- 增量变更
数据库模式变更应无法撤销 取而代之使用的是复式记账 使用新操作来回退旧操作
渐进式变更:
用户ID, 全名 -- 开始状态↓用户ID, 全名, 名, 姓 -- 过渡状态↓用户ID, 名, 姓 -- 结束状态
数据耦合
- 事务
- 不进行数据库重构
随着现实世界的演进 若不对数据库模式做出变更 那现有的数据会与过去的仍存在耦合
架构迁移
拆分
定义新的服务边界,根据:
- 业务功能
- 事务边界
- 部署目标
对于共享的模块,在微服务中通过复制代码来进行共享
模式
- 拆迁者模式(直接替换转移) 风险很大
- 绞杀者模式(逐步替换转移,直至旧系统全部消失)
- 修缮者模式(同上,老旧系统共存 逐步替换)
- 抽象分支 把要重构的方法重构成一个方法对象,然后提取出一个接口,待重构的方法是接口的一个实现,重构后的方法是另一个实现
- 并行修改 在类的内部新建一些方法,以提供新的接口(即扩张),然后再逐步让调用端使用新的接口(即迁移),当所有调用端都使用新的接口后,就删除旧的接口(即收缩) 上面的数据库渐进式变更就是一种实例
DDD
- 气泡上下文:用防腐层(Anticorruption Layer)隔离开的一个小的限界上下文,遇到一个新的需求时,可以评估这个需求,将它放到气泡上下文中
- 自治气泡:有自己的数据库,与遗留系统是弱耦合的,通过数据同步,无论是定时任务、事件拦截、还是CDC,重要的是隔离
构建演进式架构
不可变
可变会使系统变得脆弱
- 不可变的基础设施
- 服务模板
决策可逆
- 蓝绿部署
- AB测试
- 功能开关
意识到未知
软件总会一直变化 将演进能力贯彻到软件的整个生命周期里
防腐
构建接口来隔离无法受控的第三方接口的变化
可牺牲
随着准备推倒系统重建
依赖管理
- 将依赖更新集成到流水线
陷阱与反模式
围绕某一具体平台技术
抽象泄漏
- 组件对于其的依赖了解过多
低代码/4GL
这类工具注定无法完成过于定制化的需求
滥用复用
复用是有代价的 复用性越高 易用性越低
- 重复优于耦合(微服务)
为新而新
组织架构跟软件架构不一致
发布过慢
产品定制
- 不同的条件化功能会加重测试负担
报表
- 其几乎耦合了整个系统的大部分
看的太远
遗留系统现代化
遗留系统: 仍在使用过时技术但至关重要的系统
现代化的价值:
- 数据资产
- 蕴含的丰富业务知识
遗留系统的认知负载:
- 分布在各处的业务知识
- 难以获取的系统知识:具体实现细节,包括模块的划分、架构的取舍,以及每一个技术决策的原因
遗留系统改造的难点:
- 容易出错
- 软件复杂度高,不知从何入手
- 复杂度不会消失,只会从一个地方转移到另外一个地方
遗留系统的增量演进:
- 代码增量:先把代码复制出来一份,在复制的代码处进行重构。等重构完毕,再通过某种开关,来控制新旧代码的切换
- 架构增量:架构演进
四个现代化
- 代码现代化:[重构](/软件工程/软件设计/代码质量/代码重构.html)提升代码质量,增强可测试性,为了对遗留系统进行测试,要在不修改其代码的情况下进行测试,此时可以通过代码的接缝来进行测试:可以被修改的状态、可以操控的行为、以及代码模块本身暴露的接口,通过对这些对象操控来进行测试验证
- 架构现代化:进行更合理的规划,清晰明确各模块组件职责及边界
- DevOps现代化:自动化手段降低开发运维成本
- 团队结构现代化:更合理的团队结构使得演进更容易
策略
- 退休:评估完工作量、使用情况和业务价值之后,选择完全停止使用的一种策略
- 维持原样:对于尚可满足使用的遗留系统,保持系统当前的状态不做任何修改或更新
- 封装:将遗留系统中的数据或者功能封装成 API,供外部调用
- 替换运行平台:只需要对代码做少量更改,以适配新的平台。这样,只通过较小的成本就可以降低基础设施的成本,并提高性能
- 基础设施迁移:完全不需要修改代码,而只需要迁移部署的环境
- 重构:不改变系统外部行为的前提下,对代码或架构进行调整、优化,以偿还拖欠已久的技术债务、改善非功能需求、提升系统健康度。
- 重写:对应用程序的某个组件或某个服务的重新设计或重写或彻底淘汰应用程序的所有组件,去构建或购买新的软件
持续集成
遗留系统加入持续集成流水线,由于历史原因,没有单元测试,在现代化早期可以裁剪掉单元测试,只要初步引入工具链,简单的静态代码扫描,就是一种进步
服务架构演进
Unix的分布式设计哲学保持接口与实现的简单性,比系统的任何其他属性,包括准确性、一致性和完整性,都来得更加重要。
传统架构 -> 分布式架构 -> SOA(Service-Oriented Architecture)架构 -> 微服务架构
原始分布式时代
上世纪7 80年代 当时计算机硬件局促的运算处理能力,已直接妨碍到了在单台计算机上信息系统软件能够达到的最大规模。为突破硬件算力的限制,各个高校、研究机构、软硬件厂商开始分头探索,寻找使用多台计算机共同协作来支撑同一套软件系统运行的可行方案
这个时代提出了RPC的雏形以及日后分布式文系统的最早实现AFS
“调用远程方法”与“调用本地方法”尽管只是两字之差,但若要同时兼顾到简单、透明、性能、正确、鲁棒、一致的话,两者的复杂度就完全不可同日而语
某个功能能够进行分布式,并不意味着它就应该进行分布式,强行追求透明的分布式操作,只会自寻苦果
这个时间段过后的一端时间 摩尔定律的黄金时代 计算机的算力不断提升 在日后的一端时间 单体软件架构还是主流
单体系统时代
在微服务盛行的这段日子 单体系统好像总是以反派身份登场 但对于小型系统 不论是开发 测试 部署,单体系统都有着不可比拟的优越性
乍一看单体架构的缺点似乎会是不可拆分 难以扩展 无法继续支撑越来越大的软件规模
但几乎所有的单体系统都会进行分层拆分:
单体系统的缺陷在于拆分之后的隔离与自治能力上的欠缺,所有的代码都会运行在同一进程空间之内
传统的架构,分为三层架构 web控制层、业务逻辑层、数据库访问层
一旦发生问题 问题就会扩散到整个系统,并且如果想要发布新版本 维护也是一个难题
为了允许程序出错,为了获得隔离、自治的能力,为了可以技术异构等目标,是继为了性能与算力之后,让程序再次选择分布式的理由
SOA时代
- 烟囱式架构:指的是一种完全不与其他相关信息系统进行互操作或者说协调工作的设计模式
- [微内核架构](/软件工程/架构模式/架构模式.html#微内核架构):也被称为插件式架构,将公共服务、数据、资源集中到一块,成为一个被所有业务系统共同依赖的核心,具体的业务系统以插件模块(Plug-in Modules)的形式存在
- 事件驱动架构:通过一个事件管道,各个自系统通过发送/接收事件的方式进行交互
SOA是集成的思想,使用ESB集成现有的老系统,是解决服务孤岛打通链条,是无奈之举
SOA的终极目标是希望总结出一套自上而下的软件研发方法论,所以SOA本身有着许多规范,但正是由于过于严格的规范定义带来过度的复杂性
微服务时代
轻量级 围绕业务 异构 自动化
在微服务的早期 它还是被作为SOA的一种补充手段
- SOA是把多个系统整合,而微服务是把单个系统拆开来
微服务追求的是更加自由的架构风格,摒弃了几乎所有SOA里可以抛弃的约束和规定
后微服务时代
从软件层面独力应对微服务架构问题,发展到软、硬一体,合力应对架构问题的时代
这里的硬件指的更多是诸如容器 虚拟化技术等为主的基础设施
为了解决在硬件上的服务治理粒度过粗的问题,这个时代完成了第二次进化,也就是服务网格的引入,到目前为止,服务网格还算是个新概念,仍然还在发展
另外一条路-Serverless
如果说微服务架构是分布式系统这条路的极致,那无服务架构,也许就是“不分布式”的云端系统这条路的起点
- 后端设施:指数据库、消息队列、日志、存储,等等这一类用于支撑业务逻辑运行,称其为后端即服务
- 函数:指的业务逻辑代码
无服务的无状态特征天生就不适合做某些事,或许在某些场景下,它会做的更好,但长期来看,还是为以服务架构为主