背景
MyStock 业务是一个 To B & To C 业务,但是更多的是 To B。To B 业务往往会持续多年,不停的演变和迭代。随着演变和迭代,会出现一个情况,前后端的关系变得异常复杂。为什么会关系会变得异常复杂了,因为接口任何一处的修改可能就会让前端页面渲染失败或者业务异常,会给产品代码难以预估的问题。在这样的背景下,构建稳定的前端应用,保证前端在长期迭代下的稳健与可扩展性变得非常重要。
困难和痛点
如图所示,在日常的代码结构中会有如下的场景,组件 A、B、C、D 分别依赖了接口A、B、C 三个接口。
当接口 A 返回结构调整时组件 A 对接口的调用方式需要调整。同样的,组件 B 与 组件 D 也要进行修改才能工作。
在真实的业务场景中,可能接口会更多,也会面临更加复杂的业务场景。并且在业务场景中还会存在接口灰度、接口多个版本(V1、V2、V3)。出于对界面稳定性及用户使用习惯的考量,前端往往会同时依赖接口的多个版本来构建界面。当部分接口需要调整下线或发生变更时,前端需要重新理解业务逻辑,并做出大量代码逻辑调整才能保证界面稳定运行。
常见的对前端造成影响的接口变更包括但不限于:
- 返回字段调整
- 返回结构的调整
- 调用方式改变
- 多版本共存使用
- 接口灰度
MyStock 作为一个 To B 业务,前端在面临的困境是由独特的前后端关系决定的。与其他领域不同,在 To B 业务中,前端通常以下游客户的身份接受后端供应商的供给,有些情况下会成为后端的跟随者。
在客户/供应商关系中,前端处于下游,而后端团队处于上游,接口内容与上线时间通常由后端团队来决定。在跟随者关系中,上游的后端团队不会去根据前端团队的需求进行任何调整,前端只能去顺应上游后端的模型。这种情况通常发生在前端无法对上游后端团队施加影响的时刻。
无论是客户/供应商关系,还是跟随者关系,前端很难或者无法决定接口的设计,虽然前端本身不会随着时间的推移而变得不可用,但相关接口却会随着时间推移而过时,前端代码会跟随接口的迭代更换逐步腐烂,最终难逃被迫重写的命运。
了解防腐层
早在 Windows 诞生之前,工程师为了解决上文中硬件、固件与软件的可维护性问题,引入了 HAL(Hardware Abstraction Layer)的概念, HAL 为软件提供服务并且屏蔽了硬件的实现细节,使得软件不必由于硬件或者固件的变更而频繁修改。
HAL 的设计思想在领域驱动设计(DDD) 中又被称为防腐层(Anticorruption Layer)。在 DDD 定义的多种上下文映射关系中,防腐层是最具有防御性的一种。它经常被使用在下游团队需要阻止外部技术偏好或者领域模型入侵的情况,可以帮助很好地隔离上游模型与下游模型。
创建一个防腐层,以根据客户端自己的域模型为客户提供功能。该层通过其现有接口与另一个系统进行通信,几乎不需要或不需要对其进行任何修改。因此,防腐层隔离不仅是为了保护您免受混乱的代码的侵害,还在于分离不同的域并确保它们在将来保持分离。防腐层是将一个域映射到另一个域,这样使用第二个域的服务就不必被第一个域的概念“破坏”。
在不共享相同语义的不同子系统之间实施外观或适配器层。 此层转换一个子系统向另一个子系统发出的请求。 使用防腐层(Anti-corruption layer)模式可确保应用程序的设计不受限于对外部子系统的依赖。
防腐层设计
我们可以在前端中引入防腐层的概念,降低或避免当前后端的上下文映射接口变更对前端代码造成的影响。
防腐层目的
- 防腐层两方的系统解耦,隔离双方变更的影响,允许双方独立演进;
- 防腐层允许其它的外部系统能够在不改变现有系统的领域层的前提下,与该系统实现无缝集成,从而降低系统集成的开发工作量。
防腐层实现
在行业内有很多种方式可以实现防腐层,无论是近几年大火的 GraphQL 还是 BFF 都可以作为备选方案,但是技术选型同样受限于业务场景。与 To C 业务完全不同,在 To B 业务中,前后端的关系通常为客户/供应商或者跟随者/被跟随者的关系。在这种关系下,寄希望于后端配合前端对接口进行 GraphQL 改造已经变得不太现实,而 BFF 的构建一般需要额外的部署资源及运维成本。
在上述情况下,在浏览器端构建防腐层是更为可行的方案,但是在浏览器中构建防腐层同样面临挑战。
无论是 React、Angular 还是 Vue 均有无数的数据层解决方案,从 Mobx、Redux、Vuex 等等,这些数据层方案对视图层实际上都会有入侵,有没有一种防腐层解决方案可以与视图层彻底解耦呢?以 RxJS 为代表的 Observable 方案在这时可能是最好的选择。
我们选择 RxJS 主要基于以下几点考虑:
- 统一不同数据源的能力:RxJS 可以将 甚至用户操作、页面点击等转换为统一的 Observable 对象。
- 统一不同类型数据的能力:RxJS 将异步数据和同步数据统一为 Observable 对象。
- 丰富的数据加工能力:RxJS 提供了丰富的 Operator 操作符,可以对 Observable 在订阅前进行预先加工。
不入侵前端架构:RxJS 的 Observable 可以与 Promise 互相转换,这意味着 RxJS 的所有概念可以被完整封装在数据层,对视图层可以只暴露 Promise。
当在引入 RxJS 将所有类型的接口转换为 Observable 对象后,前端的视图组件将仅依赖 Observable,并与接口实现的细节解耦,同时,Observable 可以与 Promise 相互转换,在视图层获得的是单纯的 Promise,可以与任意数据层方案和框架搭配使用。
参考
- https://www.cnblogs.com/BlogNetSpace/p/15100182.html
- https://www.freesion.com/article/2309291371/
- https://blog.csdn.net/weixin_42190471/article/details/123249862
- https://juejin.cn/post/7078192729296994312
- https://baike.baidu.com/item/%E7%A1%AC%E4%BB%B6%E6%8A%BD%E8%B1%A1%E5%B1%82/9084603?fr=aladdin
- https://zhuanlan.zhihu.com/p/484237327