基于 Observable 构建前端防腐策略
简介:To B 业务的生命周期与迭代通常会持续多年,随着产品的迭代与演进,以接口调用为核心的前后端关系会变得非常复杂。在多年迭代后,接口的任何一处修改都可能给产品带来难以预计的问题。在这种情况下,构建更稳健的前端应用,保证前端在长期迭代下的稳健与可拓展性就变得非常重要。本文将重点介绍如何利用接口防腐策略避免或减少接口变更对前端的影响。
作者 | 谢亚东
来源 | 阿里技术公众号
To B 业务的生命周期与迭代通常会持续多年,随着产品的迭代与演进,以接口调用为核心的前后端关系会变得非常复杂。在多年迭代后,接口的任何一处修改都可能给产品带来难以预计的问题。在这种情况下,构建更稳健的前端应用,保证前端在长期迭代下的稳健与可拓展性就变得非常重要。本文将重点介绍如何利用接口防腐策略避免或减少接口变更对前端的影响。
一 困境与难题
为了更清晰解释前端面临的难题,我们以 To B 业务中常见的仪表盘页面为例,该页面包含了可用内存、已使用内存和已使用的内存占比三部分信息展示。
经过数年甚至更长时间的迭代后,接口会逐步产生多个版本,出于对界面稳定性及用户使用习惯的考量,前端往往会同时依赖接口的多个版本来构建界面。当部分接口需要调整下线或发生变更时,前端需要重新理解业务逻辑,并做出大量代码逻辑调整才能保证界面稳定运行。
- 返回字段调整
- 调用方式改变
- 多版本共存使用
当前端面对的是平台型业务时,此类问题会变得更为棘手。平台型产品会对一种或多种底层引擎进行封装,例如机器学习平台可能会基于 TensorFlow、Pytorch 等机器学习引擎搭建,实时计算平台可能基于 Flink、Spark 等计算引擎搭建。
虽然平台会对引擎的大部分接口进行上层封装,但不可避免的仍然会有部分底层接口会直接被透传到前端,在这个时候,前端不仅要应对平台的接口变更,还会面临着开源引擎接口的变更带来的挑战。
在客户/供应商关系中,前端处于下游,而后端团队处于上游,接口内容与上线时间通常由后端团队来决定。
在跟随者关系中,上游的后端团队不会去根据前端团队的需求进行任何调整,前端只能去顺应上游后端的模型。这种情况通常发生在前端无法对上游后端团队施加影响的时刻,例如前端需要基于开源项目的接口设计界面,或者是后端团队的模型已经非常成熟且难以修改时。
软件应当是一种使用周期很长的东西,而固件会随着硬件的演进而淘汰过时,但事实上的情况是,虽然软件本身不会随着时间推移而磨损,但硬件及其固件却会随时间推移而过时,随即也需要对软件做相应的改动。
无论是客户/供应商关系,还是跟随者关系,正如软件无法决定硬件的发展与迭代一样,前端也很难或者无法决定引擎与接口的设计,虽然前端本身不会随着时间的推移而变得不可用,但技术引擎及相关接口却会随着时间推移而过时,前端代码会跟随技术引擎的迭代更换逐步腐烂,最终难逃被迫重写的命运。
二 防腐层设计
早在 Windows 诞生之前,工程师为了解决上文中硬件、固件与软件的可维护性问题,引入了 HAL(Hardware Abstraction Layer)的概念, HAL 为软件提供服务并且屏蔽了硬件的实现细节,使得软件不必由于硬件或者固件的变更而频繁修改。
我们可以在前端中引入防腐层的概念,降低或避免当前后端的上下文映射接口变更对前端代码造成的影响。
在上述情况下,在浏览器端构建防腐层是更为可行的方案,但是在浏览器中构建防腐层同样面临挑战。
无论是 React、Angular 还是 Vue 均有无数的数据层解决方案,从 Mobx、Redux、Vuex 等等,这些数据层方案对视图层实际上都会有入侵,有没有一种防腐层解决方案可以与视图层彻底解耦呢?以 RxJS 为代表的 Observable 方案在这时可能是最好的选择。
- 统一不同数据源的能力:RxJS 可以将 websocket、http 请求、甚至用户操作、页面点击等转换为统一的 Observable 对象。
- 统一不同类型数据的能力:RxJS 将异步数据和同步数据统一为 Observable 对象。
- 丰富的数据加工能力:RxJS 提供了丰富的 Operator 操作符,可以对 Observable 在订阅前进行预先加工。
- 不入侵前端架构:RxJS 的 Observable 可以与 Promise 互相转换,这意味着 RxJS 的所有概念可以被完整封装在数据层,对视图层可以只暴露 Promise。
当在引入 RxJS 将所有类型的接口转换为 Observable 对象后,前端的视图组件将仅依赖 Observable,并与接口实现的细节解耦,同时,Observable 可以与 Promise 相互转换,在视图层获得的是单纯的 Promise,可以与任意数据层方案和框架搭配使用。
除了转换为 Promise 之外,开发者也可以与 RxJS 在渲染层的解决方案,例如 rxjs-hooks 混用,获得更好的开发体验。
三 防腐层实现
参照上文的防腐层设计,我们在开头的仪表盘项目中实现以 RxJS Observable 为核心的防腐层代码。
其中防腐层的核心代码如下
MemoryUsagePercent 的实现代码如下,此时该组件将不再依赖具体的接口,而直接依赖防腐层的实现。
1 返回字段调整
返回字段变更时,防腐层可以有效拦截接口对组件的影响,当 /api/v2/quota/free 与 /api/v2/quota/usage 的返回数据变更为以下结构时
我们只需要调整防腐层的两行代码,注意此时我们的上层封装的 getMemoryUsagePercent 基于 Observable 构建所以不需要进行任何改动。
在 Observable 化的防腐层中,会存在高阶 Observable 与 低阶 Observable 两种设计,在上文的例子中,Free Observable 和 Usage Observable 为低阶封装,而 Percent Observable 利用 Free 和 Usage 的 Observable 进行了高阶封装,当低阶封装改动时,由于 Observable 本身的特性,高阶封装经常是不需要进行任何改动的,这也是防腐层给我们带来的额外好处。
当调用方式发生改变时,防腐层同样可以发挥作用。/api/v3/memory 直接返回了 free 与 usage 的数据,接口格式如下。
防腐层代码只需要进行如下更新,就可以保障组件层代码无需修改。
3 多版本共存使用
当前端代码需要在多套环境下部署时,部分环境下 v3 的接口可用,而部分环境下只有 v2 的接口部署,此时我们依然可以在防腐层屏蔽环境的差异。
通过 race 操作符,当 v2 与 v3 任何一个版本的接口可用时,防腐层都可以正常工作,在组件层无需再关注接口受环境的影响。
四 额外应用
防腐层不仅仅是多了一层对接口的封装与隔离,它还能起到以下作用。
1 概念映射
接口语义与前端需要数据的语义有时并不能完全对应,当在组件层直接调用接口时,所有开发者都需要对接口与界面的语义映射足够了解。有了防腐层后,防腐层提供的调用方法包含了数据的真实语义,减少了开发者的二次理解成本。
2 格式适配
在很多情况下,接口返回的数据结构与格式与前端需要的数据格式并不符合,通过在防腐层增加数据转换逻辑,可以降低接口数据对业务代码的入侵。在以上的案例里,我们封装了 getMemoryUsagePercent 的数据返回,使得组件层可以直接使用百分比数据,而不需要再次进行转换。
3 接口缓存
对于多种业务依赖同一接口的情况,我们可以通过防腐层增加缓存逻辑,从而有效降低接口的调用压力。
与格式适配类似,将缓存逻辑封装在防腐层可以避免组件层对数据的二次缓存,并可以对缓存数据集中管理,降低代码的复杂度,一个简单的缓存示例如下。
4 稳定性兜底
当接口稳定性较差时,通常的做法是在组件层对 response error 的情况进行处理,这种兜底逻辑通常比较复杂,组件层的维护成本会很高。我们可以通过防腐层对稳定性进行兜底,当接口出错时可以返回兜底业务数据,由于兜底数据统一维护在防腐层,后续的测试与修改也会更加方便。在上文中的多版本共存的防腐层中,增加以下代码,此时即使 v2 和 v3 接口都无法返回数据,前端仍然可以保持可用。
5 联调与测试
接口和前端可能会存在并行开发的状态,此时,前端的开发并没有真实的后端接口可用。与传统的搭建 mock api 的方式相比,在防腐层直接对数据进行 mock 是更方便的方案。
在防腐层对数据进行 mock 也可以用于对页面的测试,例如 mock 大量数据对页面性能影响。
五 总结
在本文中我们介绍了以下内容:
- 前端面对接口频繁变动时的困境及原因如何
- 防腐层的设计思想与技术选型
- 使用 Observable 实现防腐层的代码示例
- 防腐层的额外作用
请读者注意,只在特定的场景下引入前端防腐层才是合理的,即前端处于跟随者或供应商/客户关系中,且面临大量接口无法保障稳定和兼容。如果在防腐层可以在后端 Gateway 构建,或者接口数量较少时,引入防腐层带来的额外成本会大于其带来的好处。
RxJS 在防腐层构建场景下提供的更多的是 Observable 化的能力,如果读者不需要复杂的 operators 转换工具,也可以自行构建 Observable 构建方案,事实上只需要 100 行的代码就可以实现
改造后的前端架构将不再直接依赖接口实现,不会入侵现有前端数据层设计,还可以承担概念映射、格式适配、接口缓存、稳定性兜底以及协助联调测试等工作。文中所有的示例代码都可以在仓库
本文为阿里云原创内容,未经允许不得转载。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2020-03-23 五个问答,告诉你阿里云对象存储如何助力钉钉战胜业务洪峰
2020-03-23 别琢磨了,企业高效灵活运作的秘密拿走:企业邮箱5折起!分享会场抽取苹果手机和猫超卡!