《演进式架构》读书笔记

第一章:软件架构

以前我们认为软件架构是应该一开始就设计好的,当你编写第一行代码的时候,就应该遵循架构规则。但是架构是服务于需求的,需求又是不断变化的,这必然导致一开始设计的架构无法适配新的业务,因此我们需要演进式架构。

架构师的首要任务是理解业务,也即是领域需求,这是架构的基础。但是架构除了解决业务需求外,还需要结合非功能性需求(例如可用性,安全性,性能等)设计适当方案,随着时间推移,如何保护架构的这些特性,本书提出”演进能力“特征。

1.1 演进式架构

软件体系是由工具(webpack、cli)、框架(Vue、react)、库(vuex、vue-router)以及最佳实践构成。这些工具组成的软件体系,内部实现了平衡,开发人员在该平衡环境添加代码。然而,平衡是动态的,例如引入新的框架,新的类库都会打破平衡,我们需要调整结构,让其重新回到平衡状态。所以在不断变化的环境中,是没法做长期规划的,能做的只有当变化出现时,调整架构适应变化以达新的平衡。

另外,就算完成了架构设计,架构也会随着时间推移而退化,例如常见的分层架构,展示层、中间层、数据持久层,有时候开发人员基于性能考虑,从展示层直接访问数据持久层,这会导致分层架构毫无意义。所以我们需要考虑如何保护架构,架构的演进能力是一种元特征和保护架构特征的架构封装器。

演进式架构的定义:

支持跨多个维度的引导性增量变更。

1.2 增量变更

增量变更描述软件两个方面:如何给软件添加新功能和如何部署软件。

允许增量变更的架构易于演进,当你打算迁移新服务的时候,可以保持原服务的继续使用,同时监控原服务的引用情况,如果原服务没有引用,即可删除。

1.3 引导性变更

一旦架构确定下来,为了防止架构腐化,需要引入评估机制,包括度量、测试和其他验证工具保护架构基本特征,把这种机制成为适应度函数

1.4 多个架构维度

架构师不能只考虑技术维度,同时需要考虑一些影响架构演进能力的常见维度如:技术、数据、安全、运维与系统。 从概念上划分维度方法有很多,例如IEEE的软件架构划分为:逻辑视图、开发视图、进程视图、物理视图。

架构确定了架构特征,例如:可伸缩性、数据、安全、性能、合法性;那就需要设计相应的适应度函数保护其完整性。

1.5 康威定律

康威提出:社会结构,特别是人和人的沟通途径将不可避免的影响最终产品的设计。 例如常见的技术团队划分为前端负责UI和后端负责接口,他们之前沟通的途径就是接口,为了适配这种沟通方式,可以通过契约模式做限制。

但是以技术职能划分团队,会导致各团队往往专注于各自交付的内容,而不关注端到端的特性价值,这可能导致不能高效协作;例如后端交付的接口,如果不符合前端界面的使用,前端需要花费大量时间处理这些内容,影响总体开发效率。

康威在论文提到:”每个新的团队组件,其他团队的职责范围会缩小,能够有效执行的可选设计方案也会随之变少“

也就是说,人们很难改变职责范围外的事情,就像一些大企业,职级划分的越细,越容易出现部门墙,协调一件事情会变得越复杂。

为了解决这个问题,作者提出”康威逆定律“,公司围绕服务边界构建团队,而不是按孤立的技术架构来划分,例如”微服务“架构。

第二章:适应度函数

架构往往需要平衡不同的特征,适应度函数提供了评估的标准;如果单一的适应度函数在所对应的维度出现冲突,可通过全系统适应度函数评估。

2.1 什么是适应度函数

现实架构是由不同维度构成的,包括性能、可靠性、安全性、可操作性、代码规范和集成等方面的需求。我们希望通过适应性函数表示每一项指标。假设评估系统性能,可以通过收集系统执行响应速度。假设评估代码规范,可以收集代码圈复杂度评估;这些评估的方法我们都成为适应度函数。

适应度函数最终目的是为了引导演进式架构,指出架构中对我们重要的部分,使我们能够在软件开发过程中作出各种关键又令人烦恼的权衡。

2.2 适应度函数分类

原子适应函数和整体适应度函数:原子适应函数针对单一上下文,用来验证架构某一维度,例如验证某个模块耦合度的单元测试;整体适应度函数在共享的上下文运行,用来验证架构多个维度,比如安全性和伸缩性。

触发式适应度函数和持续式适应度函数触发式适应度函数基于特定的事件执行,例如开发人员执行单元测试;持续式不是按计划执行,而是持续不断的验证架构的某个方面,比如事务处理速度。另外监控驱动开发也是持续式的一种,通过系统运行时收集日志数据数据。

静态适应度函数和动态适应度函数静态适应度结果是固定的,比如测试的二进制结果--成功或者失败,或者用来衡量代码质量的圈复杂度,而且这些一般会集成到自动化构建时自动检查;动态适应度依赖上下文,例如运行性能,他会应用的规模,规模越大允许较低的性能,他是动态变化的。

2.3 尽早确定适应度函数

团队应该尽早确定适应度函数,这有助于在实现架构变更时评估变更的价值。另外也有助于更早的设置高风险工作的优先级。

适应度函数可以根据重要程度分为三类:关键维度(这些维度对做出技术决策或设计至关重要)、相关维度(这些维度需要在特性级别考虑,但不太会指导架构决策,例如围绕代码质量的指标)、不相关维度

适应度函数的执行结果可视化至明显的公共区域,能使开发人员记得在日常编码中考虑他们,保持关键部分和相关适应度的活力。

2.4 审查适应度函数

适应度函数应该定期审查,参与审核的人员可以是业务和技术利益相关者。

第三章:实现增量变更

演进式架构是支持多个维度进行引导性增量变更的架构,增量变更包含开发运维两个方面。

3.1 构件

软件架构师需要决定系统的构成,所以需要绘制不同的图表表达架构,但是架构图不应该是二维的,而是四维的,因为我们身处四维世界,二维图表无法表达真实世界。

软件中一切都是动态的,因为世界在不断变化,要实现动态平衡,就需要不断演进。只有成功完成了架构设计、实现、升级和无法避免的变更后,架构师才能评价架构的长期有效性。

可测试性

是软件架构中一个被经常忽略的特性,因为缺乏工具的支持,通常很难测试架构的各个部分。但是架构耦合性和开发规范是容易测试的,相比执行严格的开发规范(伴随着居高临下的说教),我们倾向于构建单元测试来捕捉架构违例。

部署流水线

部署流水线和持续集成相似,但是比持续集成包含更多职责,他鼓励开发人员将持续集成的任务拆分到不同阶段执行。像GOCD这个开源工具有助于构建部署流水线。

3.2 假设驱动开发和数据驱动开发

数据驱动开发: 就是系统上线后通过埋点收集用户访问数据,调整系统架构。

假设驱动开发: 就是设计两个方案,然后用过A/B发布形式,收集真实结果,决定最终方案。

第四章:架构耦合

演进式架构注重适当的耦合,即确定架构哪些部分适当耦合以最小成本获得最大收益。

模块化: 描述相关代码的逻辑分组。

组件: 是模块的物理封装。例如“服务”就是一个组件,他们在自己的地址空间运行,通过协议通讯。

不同架构的演进能力

软件架构存在的部分原因是为了实现跨特定维度的某种演进。架构模式对于成功演进至关重要,但它不是唯一决定性因素。

大泥团架构

这种架构高度耦合,变更时会产生连锁副作用,每个类都高度耦合。

增量变更: 难以做任何变更。代码散落在系统的各个角落,修改任意组件都会意外的破坏另外一个组件。

通过适应度函数引导变更: 由于没有明确定义分区,很难构建适应度函数。

适当耦合: 这种架构是不当耦合的典型。构建这样的软件没有任何架构优势。

单体架构

1.非结构化的单体结构

image.png 不同模块各自处理不同的任务,通过公共的类实现通用功能。

增量变更: 组件间高度耦合,难以独立部署某个组件。

适应度函数: 常用的引导性变对象通常会成为单体结构的致命弱点,例如伸缩性和性能。

适当耦合: 单体结构除了简单之外几乎没有内部结构,任何的代码变更都可能对某个部分产生影响。

2.分层架构

image.png

分层架构以不同技术分割到不同层,他的优点是开发者可以轻松替换不同层的技术架构,而且层与层之间互相隔离,可以提供关注点独立且分离特性。

增量变更: 虽然可以轻松替换某一层的代码实现,但是当业务发生变化时,不得不协调每一层一起变更。

适应度函数: 结构化的单体应用更便于编写适应度函数。另外可以通过隔离某一层实现测试,便于构建适应度函数。

适当耦合: 单体架构的一个优点是易于理解。开发者可以把一些设计模式应用到某一层上,某一层技术也可以轻松替换。

3.模块化的单体架构

image.png 模块化的单体架构也可以实现像微服务架构的特点,例如隔离性、独立性和小变更单元等,但前提是开发人员极其严格地处理耦合(模块间严格的可见性和连接规则)。

在项目一开始,单体架构,特别是分层单体架构是普遍的选择,因为它的结构容易理解。但是由于性能下降、代码库过大和其他一系列困难,很多单体最终被取代而走向生命尽头。

如果无法构建单体应用,为什么你认为微服务能解决问题呢?

增量变更: 模块化很容易实现增量变更;组件的可部署程度决定了增量变更的速度。

适应度函数: 因为合理的划分了组件,使得测试模拟和依赖隔离变得容易。

适当耦合: 一个设计良好的模块化单体架构是适当耦合的好例子。

4.微内核架构

微内核架构通常出现在浏览器和集成开发环境(IDE)中。 image.png 微内核架构的主要挑战围绕着契约,它是某种形式的语义耦合。插件必须和核心系统进行双向信息传递。只要插件不需要互相协调,开发人员就可以专注于插件和核心系统间的信息和版本控制。

增量变更: 因为系统大多数行为都来自插件,如果插件都是独立的,那增量变更变得非常容易。

适应度函数: 因为插件和核心系统相对独立,开发人员可以轻松创建适应度函数,插件和核心系统可以创建两套适应度函数。

适当耦合: 微内核模式明确定义了这种架构的耦合特征。协调相互依赖的插件会更难,开发人员应该通过适应度函数来将相互依赖的组件正确地集成。

开发人员应计划构建一套集成测试,把它作为整体适应度函数。当系统中存在相互依赖的插件时,开发人员还应该构建整体适应度函数来确保契约和消息的一致性。

事件驱动架构

1.代理模式

代理模式由:消息队列、始发事件、流程内事件、事件处理器组成。

image.png

增量变更: 代理模式由于其松耦合特点,较容易实现增量变更,但架构的本质是异步通讯,所以它很难测试。

适应度函数: 因为事件单个逻辑很简单,所以很容易对单个服务进行测试;但是很难编写整体适应度函数,因为整个系统服务依赖多个松散的服务。

适当耦合: 服务和他们所维持的消息契约之间存在耦合,只是功能内聚的一种形式。适应度函数运用消费者驱动的契约等技术来帮助管理集成点,避免其被破坏。

分类:
阅读
标签: