基于 Observable 构建前端防腐策略

简介:To B 业务的生命周期与迭代通常会持续多年,随着产品的迭代与演进,以接口调用为核心的前后端关系会变得非常复杂。在多年迭代后,接口的任何一处修改都可能给产品带来难以预计的问题。在这种情况下,构建更稳健的前端应用,保证前端在长期迭代下的稳健与可拓展性就变得非常重要。本文将重点介绍如何利用接口防腐策略避免或减少接口变更对前端的影响。

作者 | 谢亚东
来源 | 阿里技术公众号

To B 业务的生命周期与迭代通常会持续多年,随着产品的迭代与演进,以接口调用为核心的前后端关系会变得非常复杂。在多年迭代后,接口的任何一处修改都可能给产品带来难以预计的问题。在这种情况下,构建更稳健的前端应用,保证前端在长期迭代下的稳健与可拓展性就变得非常重要。本文将重点介绍如何利用接口防腐策略避免或减少接口变更对前端的影响。

一 困境与难题

为了更清晰解释前端面临的难题,我们以 To B 业务中常见的仪表盘页面为例,该页面包含了可用内存、已使用内存和已使用的内存占比三部分信息展示。

此时前端组件与接口之间的依赖关系如下图所示。

当接口返回结构调整时MemoryFree 组件对接口的调用方式需要调整。同样的,MemoryUsage 与 MemoryUsagePercent 也要进行修改才能工作。

真实的 To B 业务面临的接口可能会有数百个,组件与接口的集成逻辑也远比以上的例子要复杂。

经过数年甚至更长时间的迭代后,接口会逐步产生多个版本,出于对界面稳定性及用户使用习惯的考量,前端往往会同时依赖接口的多个版本来构建界面。当部分接口需要调整下线或发生变更时,前端需要重新理解业务逻辑,并做出大量代码逻辑调整才能保证界面稳定运行。

常见的对前端造成影响的接口变更包括但不限于:

  • 返回字段调整
  • 调用方式改变
  • 多版本共存使用

当前端面对的是平台型业务时,此类问题会变得更为棘手。平台型产品会对一种或多种底层引擎进行封装,例如机器学习平台可能会基于 TensorFlow、Pytorch 等机器学习引擎搭建,实时计算平台可能基于 Flink、Spark 等计算引擎搭建。

虽然平台会对引擎的大部分接口进行上层封装,但不可避免的仍然会有部分底层接口会直接被透传到前端,在这个时候,前端不仅要应对平台的接口变更,还会面临着开源引擎接口的变更带来的挑战。

前端在面临的困境是由独特的前后端关系决定的。与其他领域不同,在 To B 业务中,前端通常以下游客户的身份接受后端供应商的供给,有些情况下会成为后端的跟随者。

在客户/供应商关系中,前端处于下游,而后端团队处于上游,接口内容与上线时间通常由后端团队来决定。

在跟随者关系中,上游的后端团队不会去根据前端团队的需求进行任何调整,前端只能去顺应上游后端的模型。这种情况通常发生在前端无法对上游后端团队施加影响的时刻,例如前端需要基于开源项目的接口设计界面,或者是后端团队的模型已经非常成熟且难以修改时。

《架构整洁之道》的作者描述过这样一个嵌入式架构设计的难题,与上文我们描述的困境十分类似。

软件应当是一种使用周期很长的东西,而固件会随着硬件的演进而淘汰过时,但事实上的情况是,虽然软件本身不会随着时间推移而磨损,但硬件及其固件却会随时间推移而过时,随即也需要对软件做相应的改动。

无论是客户/供应商关系,还是跟随者关系,正如软件无法决定硬件的发展与迭代一样,前端也很难或者无法决定引擎与接口的设计,虽然前端本身不会随着时间的推移而变得不可用,但技术引擎及相关接口却会随着时间推移而过时,前端代码会跟随技术引擎的迭代更换逐步腐烂,最终难逃被迫重写的命运。

二 防腐层设计

早在 Windows 诞生之前,工程师为了解决上文中硬件、固件与软件的可维护性问题,引入了 HAL(Hardware Abstraction Layer)的概念, HAL 为软件提供服务并且屏蔽了硬件的实现细节,使得软件不必由于硬件或者固件的变更而频繁修改。

HAL 的设计思想在领域驱动设计(DDD) 中又被称为防腐层(Anticorruption Layer)。在 DDD 定义的多种上下文映射关系中,防腐层是最具有防御性的一种。它经常被使用在下游团队需要阻止外部技术偏好或者领域模型入侵的情况,可以帮助很好地隔离上游模型与下游模型。

我们可以在前端中引入防腐层的概念,降低或避免当前后端的上下文映射接口变更对前端代码造成的影响。

在行业内有很多种方式可以实现防腐层,无论是近几年大火的 GraphQL 还是 BFF 都可以作为备选方案,但是技术选型同样受限于业务场景。与 To C 业务完全不同,在 To B 业务中,前后端的关系通常为客户/供应商或者跟随者/被跟随者的关系。在这种关系下,寄希望于后端配合前端对接口进行 GraphQL 改造已经变得不太现实,而 BFF 的构建一般需要额外的部署资源及运维成本。

在上述情况下,在浏览器端构建防腐层是更为可行的方案,但是在浏览器中构建防腐层同样面临挑战。

无论是 React、Angular 还是 Vue 均有无数的数据层解决方案,从 Mobx、Redux、Vuex 等等,这些数据层方案对视图层实际上都会有入侵,有没有一种防腐层解决方案可以与视图层彻底解耦呢?以 RxJS 为代表的 Observable 方案在这时可能是最好的选择。

RxJS 是 ReactiveX 项目的 JavaScript 实现,而 ReactiveX 最早是 LINQ 的一个扩展,由微软的架构师 Erik Meijer 领导的团队开发。该项目目标是提供一致的编程接口,帮助开发者更方便的处理异步数据流。目前 RxJS 在开发中经常被作为响应式编程开发工具使用,但是在构建防腐层的场景中,RxJS 代表的 Observable 方案同样可以发挥巨大作用。

我们选择 RxJS 主要基于以下几点考虑:

  • 统一不同数据源的能力: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 为核心的防腐层代码。

其中防腐层的核心代码如下

export function getMemoryFreeObservable(): Observable<number> {return fromFetch("/api/v1/memory/free").pipe(mergeMap((res) => res.json()));
}export function getMemoryUsageObservable(): Observable<number> {return fromFetch("/api/v1/memory/usage").pipe(mergeMap((res) => res.json()));
}export function getMemoryUsagePercent(): Promise<number> {return lastValueFrom(forkJoin([getMemoryFreeObservable(), getMemoryUsageObservable()]).pipe(map(([usage, free]) => +((usage / (usage + free)) * 100).toFixed(2))));
}export function getMemoryFree(): Promise<number> {return lastValueFrom(getMemoryFreeObservable());
}export function getMemoryUsage(): Promise<number> {return lastValueFrom(getMemoryUsageObservable());
}

MemoryUsagePercent 的实现代码如下,此时该组件将不再依赖具体的接口,而直接依赖防腐层的实现。

function MemoryUsagePercent() {const [usage, setUsage] = useState<number>(0);useEffect(() => {(async () => {const result = await getMemoryUsagePercent();setUsage(result);})();}, []);return <div>Usage: {usage} %</div>;
}export default MemoryUsagePercent;

1 返回字段调整

返回字段变更时,防腐层可以有效拦截接口对组件的影响,当 /api/v2/quota/free 与 /api/v2/quota/usage 的返回数据变更为以下结构时

{requestId: string;data: number;
}

我们只需要调整防腐层的两行代码,注意此时我们的上层封装的 getMemoryUsagePercent 基于 Observable 构建所以不需要进行任何改动。

export function getMemoryUsageObservable(): Observable<number> {return fromFetch("/api/v2/memory/free").pipe(mergeMap((res) => res.json()),
+    map((data) => data.data));
}export function getMemoryUsageObservable(): Observable<number> {return fromFetch("/api/v2/memory/usage").pipe(mergeMap((res) => res.json()),
+    map((data) => data.data));
}

在 Observable 化的防腐层中,会存在高阶 Observable 与 低阶 Observable 两种设计,在上文的例子中,Free Observable 和 Usage Observable 为低阶封装,而 Percent Observable 利用 Free 和 Usage 的 Observable 进行了高阶封装,当低阶封装改动时,由于 Observable 本身的特性,高阶封装经常是不需要进行任何改动的,这也是防腐层给我们带来的额外好处。

2 调用方式改变

当调用方式发生改变时,防腐层同样可以发挥作用。/api/v3/memory 直接返回了 free 与 usage 的数据,接口格式如下。

{requestId: string;data: {free: number;usage: number;}
}

防腐层代码只需要进行如下更新,就可以保障组件层代码无需修改。

export function getMemoryObservable(): Observable<{ free: number; usage: number }> {return fromFetch("/api/v3/memory").pipe(mergeMap((res) => res.json()),map((data) => data.data));
}export function getMemoryFreeObservable(): Observable<number> {return getMemoryObservable().pipe(map((data) => data.free));
}export function getMemoryUsageObservable(): Observable<number> {return getMemoryObservable().pipe(map((data) => data.usage));
}export function getMemoryUsagePercent(): Promise<number> {return lastValue(getMemoryObservable().pipe(map(({ usage, free }) => +((usage / (usage + free)) * 100).toFixed(2))));
}

3 多版本共存使用

当前端代码需要在多套环境下部署时,部分环境下 v3 的接口可用,而部分环境下只有 v2 的接口部署,此时我们依然可以在防腐层屏蔽环境的差异。

export function getMemoryLegacyObservable(): Observable<{ free: number; usage: number }> {const legacyUsage = fromFetch("/api/v2/memory/usage").pipe(mergeMap((res) => res.json()));const legacyFree = fromFetch("/api/v2/memory/free").pipe(mergeMap((res) => res.json()));return forkJoin([legacyUsage, legacyFree], (usage, free) => ({free: free.data.free,usage: usage.data.usage,}));
}export function getMemoryObservable(): Observable<{ free: number; usage: number }> {const current = fromFetch("/api/v3/memory").pipe(mergeMap((res) => res.json()),map((data) => data.data));return race(getMemoryLegacyObservable(), current);
}export function getMemoryFreeObservable(): Observable<number> {return getMemoryObservable().pipe(map((data) => data.free));
}export function getMemoryUsageObservable(): Observable<number> {return getMemoryObservable().pipe(map((data) => data.usage));
}export function getMemoryUsagePercent(): Promise<number> {return lastValue(getMemory().pipe(map(({ usage, free }) => +((usage / (usage + free)) * 100).toFixed(2))));
}

通过 race 操作符,当 v2 与 v3 任何一个版本的接口可用时,防腐层都可以正常工作,在组件层无需再关注接口受环境的影响。

四 额外应用

防腐层不仅仅是多了一层对接口的封装与隔离,它还能起到以下作用。

1 概念映射

接口语义与前端需要数据的语义有时并不能完全对应,当在组件层直接调用接口时,所有开发者都需要对接口与界面的语义映射足够了解。有了防腐层后,防腐层提供的调用方法包含了数据的真实语义,减少了开发者的二次理解成本。

2 格式适配

在很多情况下,接口返回的数据结构与格式与前端需要的数据格式并不符合,通过在防腐层增加数据转换逻辑,可以降低接口数据对业务代码的入侵。在以上的案例里,我们封装了 getMemoryUsagePercent 的数据返回,使得组件层可以直接使用百分比数据,而不需要再次进行转换。

3 接口缓存

对于多种业务依赖同一接口的情况,我们可以通过防腐层增加缓存逻辑,从而有效降低接口的调用压力。

与格式适配类似,将缓存逻辑封装在防腐层可以避免组件层对数据的二次缓存,并可以对缓存数据集中管理,降低代码的复杂度,一个简单的缓存示例如下。

class CacheService {private cache: { [key: string]: any } = {};getData() {if (this.cache) {return of(this.cache);} else {return fromFetch("/api/v3/memory").pipe(mergeMap((res) => res.json()),map((data) => data.data),tap((data) => {this.cache = data;}));}}
}

4 稳定性兜底

当接口稳定性较差时,通常的做法是在组件层对 response error 的情况进行处理,这种兜底逻辑通常比较复杂,组件层的维护成本会很高。我们可以通过防腐层对稳定性进行兜底,当接口出错时可以返回兜底业务数据,由于兜底数据统一维护在防腐层,后续的测试与修改也会更加方便。在上文中的多版本共存的防腐层中,增加以下代码,此时即使 v2 和 v3 接口都无法返回数据,前端仍然可以保持可用。

  return race(getMemoryLegacy(), current).pipe(
+   catchError(() => of({ usage: '-', free: '-' })));

5 联调与测试

接口和前端可能会存在并行开发的状态,此时,前端的开发并没有真实的后端接口可用。与传统的搭建 mock api 的方式相比,在防腐层直接对数据进行 mock 是更方便的方案。

export function getMemoryFree(): Observable<number> {return of(0.8);
}export function getMemoryUsage(): Observable<number> {return of(1.2);
}export function getMemoryUsagePercent(): Observable<number> {return forkJoin([getMemoryUsage(), getMemoryFree()]).pipe(map(([usage, free]) => +((usage / (usage + free)) * 100).toFixed(2)));
}

在防腐层对数据进行 mock 也可以用于对页面的测试,例如 mock 大量数据对页面性能影响。

export function getLargeList(): Observable<string[]> {const options = [];for (let i = 0; i < 100000; i++) {const value = `${i.toString(36)}${i}`;options.push(value);}return of(options);
}

五 总结

在本文中我们介绍了以下内容:

  1. 前端面对接口频繁变动时的困境及原因如何
  2. 防腐层的设计思想与技术选型
  3. 使用 Observable 实现防腐层的代码示例
  4. 防腐层的额外作用

请读者注意,只在特定的场景下引入前端防腐层才是合理的,即前端处于跟随者或供应商/客户关系中,且面临大量接口无法保障稳定和兼容。如果在防腐层可以在后端 Gateway 构建,或者接口数量较少时,引入防腐层带来的额外成本会大于其带来的好处。

RxJS 在防腐层构建场景下提供的更多的是 Observable 化的能力,如果读者不需要复杂的 operators 转换工具,也可以自行构建 Observable 构建方案,事实上只需要 100 行的代码就可以实现 mini-rxjs - StackBlitz。

改造后的前端架构将不再直接依赖接口实现,不会入侵现有前端数据层设计,还可以承担概念映射、格式适配、接口缓存、稳定性兜底以及协助联调测试等工作。文中所有的示例代码都可以在仓库 GitHub - vthinkxie/rxjs-acl: Anti Corruption Layer with RxJS 获得。

原文链接

本文为阿里云原创内容,未经允许不得转载。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/511193.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动态卡片:富媒体内容井喷式增长下,新一代移动端动态研发的模式

简介&#xff1a;「蚂蚁动态卡片」新品发布会全程回顾 在 iOS 和 Android 系统近期推送的更迭版本中&#xff0c;系统环境已经逐渐发展出了将部分内容和服务前置化展示的趋势。 同时&#xff0c;伴随着富媒体内容井喷式增长以及内容的多样化、年轻化&#xff0c;一款移动应用…

Windows 上创建的文件,上传到 Linux 服务器,文件名乱码?

作者 | 刘光录来源 | TIAP先来说一下问题&#xff0c;在 Windows 下创建的一系列文件&#xff0c;上传到 Linux 服务器后&#xff0c;出现文件名乱码&#xff0c;导致文件无法读取的情况。事情的起因是这样的...最近有这样一个需求&#xff1a;在Java Web工程中读取本地某一个文…

阿里云成为首个通过“虚拟化云平台性能测试(大规模)”的云厂商

简介&#xff1a;2021年7月27日&#xff0c;在可信云大会上&#xff0c;中国信息通信研究院发布了《虚拟化云平台性能评估方法》&#xff0c;同时&#xff0c;宣布了阿里云成为首个通过“虚拟化云平台性能测试&#xff08;大规模&#xff09;”的云厂商&#xff0c;并获得“202…

阿里云神龙团队拿下TPCx-BB排名第一的背后技术

简介&#xff1a;阿里云自主研发的神龙大数据加速引擎获得了TPCx-BB SF3000世界排名第一的成绩。 一 背景介绍 近日&#xff0c;TPC Benchmark Express-BigBench(简称TPCx-BB)公布了最新的世界排名&#xff0c;阿里云自主研发的神龙大数据加速引擎获得了TPCx-BB SF3000排名第…

从中国移动财报透视:什么在支撑移动云发展韧性?

8 月 11 日&#xff0c;中国移动公布 2022 年中期业绩报告。报告显示&#xff0c;上半年中国移动营运收入达人民币 4969 亿元&#xff0c;同比增长 12.0%。净利润 703 亿元&#xff0c;同比增长 18.9%&#xff0c;盈利能力全球领先。其中&#xff0c;移动云收入达人民币 234 亿…

vue 前期准备,项目结构

环境 1、node -v 检测 没有就下载node&#xff0c;装到C盘 http://nodejs.cn/download/ 2、vue -v 检测 没有就 npm install -g vue/cli 或者 yarn global add vue/cli 安装脚手架 网址 https://cli.vuejs.org/zh/ 也可以从网址里找到…

如何设计一个复杂的业务系统?从对领域设计、云原生、微服务、中台的理解开始

简介&#xff1a;业级应用架构是在不断的演进和迭代&#xff0c;但是我始终感觉企业应用架构的形成过程是在一种看起来科学的方法论下&#xff0c;但是又不完全科学的过程中实现的。 作者&#xff1a;焦方飞 大年初一&#xff0c;看完中国队 1:3 越南队的比赛&#xff0c;在思…

如何从容应对复杂性

简介&#xff1a;软件的复杂性&#xff0c;是一个很泛的概念。但是一直都是开发过程中的一个难题&#xff0c;本文旨在探讨如何去从容应对复杂性。 作者 | 無涯 来源 | 阿里技术公众号 软件的复杂性&#xff0c;是一个很泛的概念。 但是一直都是开发过程中的一个难题&#xf…

阿里巴巴开源大规模稀疏模型训练/预测引擎DeepRec

简介&#xff1a;经历6年时间&#xff0c;在各团队的努力下&#xff0c;阿里巴巴集团大规模稀疏模型训练/预测引擎DeepRec正式对外开源&#xff0c;助力开发者提升稀疏模型训练性能和效果。 作者 | 烟秋 来源 | 阿里技术公众号 经历6年时间&#xff0c;在各团队的努力下&#…

产学融合如何促进技术创新,英特尔打了个样

作者 | 宋慧 出品 | CSDN 经过多年的积累与扎实科研&#xff0c;中国的科学与技术水平正在达到和超越世界一线水平。这离不开中国有基数庞大的用户和应用场景&#xff0c;为科学界和工业界提供了将理论付诸实践的机会&#xff0c;反复打磨迭代&#xff0c;以提升技术指标。 作…

3大能力升级,云效+钉钉,让研发协作更「敏捷」

简介&#xff1a;你的团队是否面临如下问题&#xff1a;没有敏捷经验&#xff0c;不知道如何落地敏捷或者敏捷实施不规范&#xff1f;研发交付过程信息更新不及时&#xff0c;无法及时跟进交付结果&#xff1f;员工入职离职&#xff0c;多套账号权限管理难&#xff1f;缺乏交付…

阿里云张献涛:自主最强DPU神龙的秘诀

简介&#xff1a;读懂云计算&#xff0c;才能看清DPU热潮。 微信公众号搜索“弹性计算百晓生”&#xff0c;获取更多云计算知识。 如果细数最近火爆的科技概念&#xff0c;DPU必然位列其中。 这是英伟达一手捧红的新造富故事&#xff0c;是2021年SoC领域最热火朝天的创业赛道…

Gartner发布2022年新兴技术成熟度曲线,推动沉浸式、AI自动化发展

编辑 | 宋慧 供稿 | Gartner Gartner 2022年新兴技术成熟度曲线列出了25项值得关注的新兴技术&#xff0c;这些技术正在推动沉浸式体验的发展和扩展、加速人工智能&#xff08;AI&#xff09;自动化并优化技术人员交付。 Gartner研究副总裁Melissa Davis表示&#xff1a;“新兴…

阿里云张献涛:公共云正不断向外延伸,一云多态是未来趋势

简介&#xff1a;一云多态是公有云的未来趋势&#xff0c;包括产品的多形态、部署的多形态和生态的多形态。 编者按&#xff1a;2021年10月22日&#xff0c;在云栖大会《一云多形态部署最佳实践》分论坛&#xff0c;阿里巴巴集团研究员、阿里云弹性计算产品线负责人张献涛发表…

4种典型限流实践保障应用高可用|云效工程师指北

简介&#xff1a;4种典型限流实践保障应用高可用&#xff0c;本文总结了一份AHAS限流实践指南&#xff0c;如果你的系统有被恶意用户攻击的风险&#xff0c;或者系统中某个应用出现异常可能会造成雪崩效应&#xff0c;那么这篇文章会对你有所帮助。 大家好&#xff0c;我叫黄博…

阿里巴巴云原生大数据运维平台 SREWorks 正式开源

简介&#xff1a;阿里巴巴云原生大数据运维平台 SREWorks&#xff0c;沉淀了团队近10年经过内部业务锤炼的 SRE 工程实践&#xff0c;今天正式对外开源&#xff0c;秉承“数据化、智能化”运维思想&#xff0c;帮助运维行业更多的从业者采用“数智”思想做好高效运维。 作者 | …

阿里云 VPC 内网性能测试最佳实践

简介&#xff1a;本文介绍了在阿里云 VPC 内网执行性能测试的方法。相较于传统的公网性能测试&#xff0c;VPC 内网性能测试完全在客户 VPC 环境进行&#xff0c;无需暴露服务到公网&#xff0c;安全性更高&#xff0c;灵活性更强。 作者:风起 背景 随着互联网的快速发展&am…

​在可视化大屏中轻松完成机器学习建模和调参应用实例

Streamlit 是一个开源 Python 库&#xff0c;可帮助开发人员为其系统创建交互式图形用户界面。它专为机器学习和数据科学家团队设计。使用 Streamlit&#xff0c;我们可以快速创建交互式 Web 应用程序并进行部署。前端工作对数据科学家来说并不重要&#xff0c;他们只想要一个小…

EventBridge 事件总线及 EDA 架构解析

简介&#xff1a;EventBridge 是事件驱动的具体落地产品&#xff0c;也是 EDA 的最佳实践方式。 作者&#xff1a;肯梦 作为 Gartner 定义的 10 大战略技术趋势之一&#xff0c;事件驱动架构&#xff08;EDA&#xff09;逐渐成为主流技术架构。根据 Gartner 的预估&#xff0…

开发者驱动的软件公司,如何赚取万亿美元?

【CSDN 编者按】在过去二十年中&#xff0c;诞生了两个价值数万亿美元的企业软件行业&#xff1a;SaaS&#xff08;Software as a Service&#xff0c;软件即服务&#xff09;软件和公有云。如今&#xff0c;第三个以开发者为核心的万亿美元软件浪潮正在来袭&#xff0c;企业该…