7月阅读周·前端架构:从入门到微前端 | 架构演进:演进式架构
背景
去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。
没有计划的阅读,收效甚微。
新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。
这个“玩法”虽然常见且板正,但是有效,已经坚持阅读六个月。
已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》。
当前阅读周书籍:《前端架构:从入门到微前端》。
架构演进:演进式架构
更新
大部分的前端应用并非一直在开发,或者编写后不再维护。它们可能开发一段时间,然后停滞一段时间,再去开发一段时间,再休息一段时间,如此往复地循环。
在修改过程中需要不断地维护,以防止这个应用变成一个遗留的、难以维护的系统。而在不同的升级方式里,我们所要面临的挑战都是不一样的:
- 依赖升级。
- 框架升级。
- 语言升级。
它们是根据各自的更新频率所产生的顺序。
依赖和框架版本升级
我们在选择依赖库的时候,通常会选择那些比较成熟的依赖。成熟的框架、组件、库,往往采用语义化版本的形式来发布。其遵循的版本号格式是:主版本号.次版本号.修订号,如1.2.3中的1是主版本号,2是次版本号,3是修订号。其版本号递增规则如下。
- 主版本号:当你做了不兼容的API修改。
- 次版本号:当你做了向下兼容的功能性新增。
- 修订号:当你做了向下兼容的问题修正。
依照这种形式,在针对依赖的升级时,我们会采取相应的应对措施:
- 当修订号发生变更时,通常只是进行一些Bug的修复,不需要我们做出响应。
- 当次版本号发生变更时,往往可能会发生一些API变更,我们要视向下兼容情况来决定是否跟进。
- 当主版本号变化时,可能有些API已经不存在了,我们需要大量地改动应用。
一旦我们决定对一个项目采取维护模式,就需要制定一些基本的策略,比如:
- 确认合理的时间间隔,如三个月一次。
- 定期检查依赖或者使用工具自动检测。
- 为更新预留时间及精力
- 准备文档策略,以记录过程中遇到的问题。
语言版本升级
对于前端而言,语言的版本升级,并不会带来太大问题。不论我们使用ES6还是TypeScript,到目前都没有发生严重的升级问题。其最后在浏览器上呈现的方式是一致的,都需要编译为ES5,都需要能兼容当前的主流特性。由于需要向下兼容ES5,在升级上不会出现太多的问题。这一点与后端语言形成了鲜明的对比,在后端语言里,如Python或者Ruby,一旦升级了某个版本,就需要重写应用。
当然,如果我们想从一种语言切换到另外一种语言,如从CoffeeScript切换到TypeScript,或者从ES6切换到TypeScript都不是一件容易的事,这种工作等价于重写应用。
遗留系统重搭
应用迁移可以分为如下几个步骤:
- 创建全新的运行环境。
- 准备接近线上环境的测试数据,如Staging。
- 执行更细粒度的版本管理控制,以方便回滚。如每一次小的变更,每一个新特性的升级,都需要版本管理工具来记录。
- 优先升级次要组件的版本,以方便向上兼容。
- 逐一升级核心框架,以查找对应的更新日志。
- 必要时自己编写依赖。在升级依赖的过程中,我们极有可能遇到的一个问题是,某个依赖不再更新,此时需要自己内联这个依赖。
- 清理掉不需要的代码和文件。
- 进行完整的用户验收测试。
- 上线前使用线上的环境进行预部署。
迁移
迁移与重构、重写最大的不同之处在于,迁移只需要改动少量的代码,就可以使旧的系统恢复生机。不需要深入理解代码中的业务逻辑,只是从架构、框架上略微对系统进行改造,就可以完成目标。此外,我们也不可能花费大量的时间和精力在重写应用上,也没有必要重构原有的代码,只需要让旧的系统集成到新的系统中继续使用即可。
架构迁移的模式
决定了迁移之后,我们可以尝试迁移应用,步骤如下:
- 构建和提取基础设施,如组件库、代码库。
- 确认用于练手的边缘应用。如果失败了、不合适了,那么可以尝试使用其他模式。
- 寻找合适的解耦方式,包含数据、事件等。
- 尝试对系统的其他部分进行拆分。
- 编写对应的文档及相应的培训。
迁移方式:微前端
迁移到微前端架构的几个步骤:
- 寻找合适的微前端技术。
- 确认可行的微前端方案。
- 尝试使用其中的某些方案。
- 对比、讨论几种不同方案的利弊。
- 决定适合当前项目实施的方案。
- 尝试在一个项目中使用新架构开发。
- 编写架构决策记录及文档,记录实施过程中常见的问题。
- 对项目成员进行相关培训。
迁移方式:寻找容器
除了微前端的方式,对于前端项目而言还有一种迁移系统的方式:寻找更大的容器。这种容器视不同的应用场景有不同的实现,如针对浏览器是iframe,针对移动端是App的WebView等。
对于浏览器应用来说,iframe是当前用得比较广泛的前端容器,它可以极大地方便我们迁移旧的前端应用。Web Components作为一个新的组件级容器,可以帮助我们兼容不同的应用和组件。只需要经过略微的修改,便可以将前端应用嵌入其他应用中去。
重构
应用的重构是在软件开发过程中的行为,而不是维护期进行的工作。只有应用还在开发进行中,我们才能深刻体会代码的坏道,以及其带来的软件架构问题。
架构重构
对于多数项目来说,重构是一个不存在的方案,原因也在于此。只有测试才能保证重要的重构能往下进行,否则只能大量依赖于手工测试。而手工测试,也是一种容易出错的方式——作为一个开发人员,我们的测试经验往往并不丰富。因此,不得不依赖于测试人员,让他们帮助我们进行回归测试。
组件提取、函数提取、样式提取
代码的复制、粘贴是我们在前端应用中经常看到的操作。开发人员在编写逻辑的时候往往倾向于直接复制代码,而非重用代码,这两种方式各有利弊。可是如果在代码逻辑上是重复的,就需要对其进行手术,删去重复的代码,以免代码进一步复杂化。回到前端的模式来看这个问题,就是三种(HTML、JavaScript、CSS)类型代码的提取:
- 组件提取。一个合理的前端应用,包含一个UI组件库。从组件的层面上看,可能出现代码重复的地方在复合组件和业务组件中。对于那些相似功能的组件,我们要决定是否提取出通用的模式,避免在业务变化时进行多处修改。对于复合组件来说,组件提取是在业务发展的过程中呈现的,需要在多次出现后进行模式提取。
- 函数提取。在我们的代码中往往包含着各种通用的处理逻辑,如统一的HTTP的错误处理,它们可以集中起来进行处理。随着我们的编程经验越来越丰富,在创建一些函数的时候就会自觉地创建xxxUtil.js、xxHelper.js,或者xx.services.ts。
- 样式提取。样式是笔者一直觉得头疼的内容。哪怕引入一个公共的样式库,我们也只能在样式上尽量统一,统一的颜色、字体大小、border等。但是,在实现业务功能的时候,非统一的样式往往会出现问题,如某一类元素的宽高、元素的定位等。由于它对我们来说并不是那么重要,所以也就不会那么在意。只是在出现需要多处修改的时候,才会重视对样式的重构。
重写
在我们决定重写之前,需要考虑几点要素:
- 重构、迁移、升级真的不能解决问题吗?
- 重写的时间预期是多少?重写的时间花费往往比预期更长,比技术上花费的时间更短,但理清业务的时间更长。
- 能接受重写的成本吗?重写不会对业务带来额外的好处,反而是在浪费时间。
- 是否整理出完整的功能列表?只有清晰的功能列表,才能保证重写不被阻碍。
- 产品是否领先于市场?在重写期间,我们的开发速度可能会落后于其他产品。
- 是否有能力同步维护两个产品和团队?在重写期间,需要在新旧应用里实现同样的功能。
- 在重写完成之前,是否可能变为遗留系统?要进行合理的技术选型,以避免重写失败。
重新架构
重搭架构
重搭架构,与重构相比,是一种更高阶的应用重构。它从应用的层级上对架构进行重新设计,而非在代码层级上进行改进。比如,结合微前端架构来拆分前端应用,并使用绞杀者模式一点一点地重写应用。
在实际的过程中,是在重新应用先前学习的相应内容:
- 重新设计构建系统,以支撑新架构。
- 设计模块化的应用架构,以帮助我们解耦模块。
- 抽象化组件、提取函数库,以减少重复代码。
- 采用微前端技术来解耦前端应用。
增量改写
增量改写,即将系统拆分为多个独立模块或应用。当我们决定对应用进行重写的时候,只需要重写其中的一部分,其他功能仍然是正常运行的。而那些需要新功能的部分,又可以运行在新的应用之上。直到有一天,我们重写完所有的模块,系统相当于重写完成了。这种模式,又称为绞杀者模式,它可以在满足系统演进的情况下实现如下目标:
- 大幅度减少对于业务的影响,降低了风险。
- 可以随时停止重写,而不影响用户使用。
- 用户、客户可以随时看到网站的变化,带来更多的价值。
- 为开发人员带来更多的自由和乐趣。
- 每个模块重写时,只关心相关部分的业务,而非所有的业务。
总结
从头写一个应用是相当有挑战的一件事。而事实是,这种挑战不一定存在,或者只是存在搭建脚手架和架构的挑战。在经历了这个过程之后,我们就会觉得项目索然无味。事实上,此时此刻,才是我们真正挑战自己的时候。见证一个系统的成长,见证它的变化——好与坏,同时不断寻找提升自己的可能性。
作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。
- 点赞
- 收藏
- 关注作者
评论(0)