如何瓦解巨石单页应用
之前通过几篇文章像大家介绍了我是如何通过微前端的手段去治理一个有几十个子业务域系统的大型 B 端系统。今天我想通过这篇文章来像大家介绍下我是如何在这样一个大型 B 端系统中去治理一个“巨石”单页子业务系统的。
先聊聊背景
作为一个前端我们大致都经历过早期的 MPA 应用演变为 SPA 应用的体验快感,也相信大家都经历过简单 SPA 演变为巨石 SPA 的痛苦。目前我在负责一个大型 B 端系统的一个子业务,这个子业务是一个大型的 SPA,3W+代码行数,子页面入口有 10+。并且因为业务属性的相似性,这个子业务系统还作为子系统服务于其他两个系统平台。从我平日的迭代开发来看,这样的应用已经“大到快不能维护了”,开发维护成本极高。
接下来我会像大家简单介绍,在拿到这样一个“巨石“单页应用,我是如何一步步做项目拆分优化治理的。
分析业务边界
首先我觉得要做的就是理清楚业务边界,我们的业务虽然作为大型 B 端系统的一个子业务,但是我们的系统内部也可以根据业务边界拆分成多个细分的子系统,接下来我从一个例子说起。
假设我们当前的业务大体如下所示,业务功能聚合在子 tab 下,每个 tab 下都有着复杂的业务逻辑,并且我们的系统也作为子业务服务于平台 A、B、C。
这时候做系统拆分的时候,很直观的就会联想到根据平台 A、B、C 把我们的系统拆分为三个业务子系统。如果说,我们的业务平台差异性很大,那么按照平台属性去做系统拆分无可厚非。但是我相信业界的 FEer 们都更倾向于 “one code,any platform”,那么我们的业务在这些平台都差异不大,甚至说只是主题色的差异,那么按照平台去做系统拆分就显得很不合理了。
接下来,我们可以考虑从页面结构来做系统拆分,可以看出目前我们应用的结构以 tab 的形式分别聚合。如果此时各个 tab 间没有复杂的事件通信等,便可以考虑以此来拆分我们的子模块。
最后还差一步,就是我们的业务属性的分析,可以更进一步的从当前的产品逻辑来入手,将更贴合或者说彼此耦合度较高的一些产品功能作为一个子模块放到一起,这样会更加利于未来代码的扩展和维护。
在我们做业务拆分的时候经常会面临着取舍,有很多细碎的小业务不知道划分到哪个子模块里,这里我说一个我经常用的方法:我会尽量保持核心业务模块的独立性,把相对无关的子业务拆分到一起,想想因为一个八竿子打不着的业务影响到核心业务大家都不想吧...
这里简单总结下:
- 分析平台差异,平台差异大可以根据平台拆分;
- 分析页面结构,如果结构清晰,可以根据结构拆分;
- 分析产品业务,将产品逻辑耦合度高的功能合并到一起;
拆分公共依赖
在我们理清了业务边界划分了业务模块之后,不要着急着去做模块拆分,这时候我们可以从这些即将要拆分的子业务模块中去找到公共依赖。
接下来简单说下我在当前业务的一个依赖拆分思路:
- 针对视图层,我们落地了组内的业务组件库,在已有 UI 组件库之外抽离了一些具有业务特征的基础业务原子组件,然后是基于原子组件封装一些复杂的业务域特有的业务组件,这些业务组件不仅服务与我们自有的业务,在其他业务域与我们业务的交集中也可以用到。
- 针对逻辑层,我们落地了业务函数库,里面包含了我们业务域通用的函数功能,接口数据结构 to 组件数据结构的各种 adapter 函数等等。
那么此时我们的系统就如下图所示:
拆分子业务模块
在梳理了业务边界和公共依赖的拆分之后,就是真正的子模块的拆分,这里我主要想说的就是怎么把拆开的各模块“聚合”起来。
不同于之前聊过的 B 端系统的治理,我们子系统中子业务模块的聚合没有太多的约束:
- 业务体量大不过整个 B 端系统,不需要复杂的路由设计;
- 不需要考虑和其他业务域间的影响,宿主环境已经为我们做好了沙箱隔离;
- 可以设计自己的通信方案,也可以适当复用宿主环境的通信方案(因为做好了业务边界的划分,子业务模块间理论上不应该存在复杂的通信场景了);
但是我们也需要针对宿主环境的架构方案来制定自己的聚合方案,这里我列举下目前我们尝试过的可行方案:
基于 npm 包架构
这种架构致力于将通用的布局组件拆分为 npm 包的形式提供给子应用。这样的架构具有以下的优点:
- 开发落地简单,子应用单独开发单独部署,仅需要引入布局组件即可;
- 不依赖宿主环境,宿主环境可以采取任意架构,这种方案都不会受到太大的影响;
但是也会有以下的缺点:
- 布局组件中不能包含过重的业务逻辑,频繁的业务更迭将导致子应用需要频繁同步更新组件版本。
- 毕竟是基于 MPA,相比于其他两种方案的无刷体验差些。
基于 iframe 架构
这种方案我就不赘述了,之前的文章已经有较为详细的架构分析了。这里我直接列举下区别于当前场景的一些缺点:
- 依赖宿主环境的架构,如果宿主环境也是 iframe 架构,那么一层层的 iframe 嵌套比较蛋疼;
基于微前端架构
同理,这种方案我就不赘述了,之前的文章已经有较为详细的架构分析了。这里我直接列举下区别于当前场景的一些缺点:
- 依赖宿主环境的架构,如果宿主也是微前端架构,因为目前我还没有尝试过微前端嵌套的场景,可行性以及未来的风险不可控。如果宿主是 iframe 架构,其实这也是一个不错的方案(完全的沙箱隔离),目前我们组内也有此种方案的尝试和落地;
最后,我们的应用按照如下层次进行拆分:
结语
以上便是我对大型 SPA 进行分层治理的一些思考,当然我所介绍的只是万千应用中的一种场景,并且真实拆分的过成功遇到的问题也比文中描述的多得多,路子很多,希望大家都能找到适合自己应用的架构设计。