Shell Component Patent
业务背景¶
- 随着乐高后台研发部门中台化战略的推进,LEGO 前端系统从 3.0 升级到 4.0,前端团队的搬迁工作包含了从 LEGO 3.0 系统上线迄今五年里,大大小小的所有需求以及不计其数的功能迭代所产生的业务逻辑。
- LEGO 3.0 系统采用的是 LEGO 前端团队自己研发的基于 JQuery 的 ArmJS 架构,在很长一段时间里表现稳定,但随着浏览器的升级及前端技术的进步,是时候逐渐退出历史舞台;而 LEGO 4.0 系统采用了在运行性能、设计思想、便于团队协同开发等方面,都表现更优异 Vue 框架。
- 经过梳理,节目库和专辑库共有 132 个列表操作,4 个大型编目插件需要迁移,其中每个操作或插件平均依赖 6 ~ 8 个索引字段分别作为入口判断添加及函数传参,而每个操作后的业务逻辑代码实现约 200 行 ~ 3000 行,大多数集中在 500 ~ 800 行之间,迁移的工作量之巨可见一斑。
- 如果采用传统的重构方案,进行愚公移山似的搬迁,首先需要从原来的代码中梳理出业务逻辑,然后理解消化这些逻辑,去掉经年累月迭代中冗余的部分,最后重新转换成新的代码,仅开发测试预估需要团队半年左右时间,还不算解决上线后试用中的 Bug;而开发的工期是紧迫的,互联网公司从来都没有多余的时间留给愚公们。
- 需要寻求一种类似沙盒的技术方案,对 LEGO 3.0 的代码套上 LEGO 4.0 的外壳,而不用关心错综复杂的业务逻辑具体如何实现;外壳既能在 LEGO 4.0 的环境里良好的运行,又能保护 LEGO 3.0 业务逻辑的完整性,同时又能提供 3.0 运行所依赖的养分(入口参数,回调等)。
- 问题在于 LEGO 3.0 不管是在最初设计框架原型,还是在后来的迭代中,都过多依赖了全局对象 (Global Object),从而对其造成了严重的污染。起初也尝试着对 3.0 的一些插件进行了几次传统的封装,但没有完全对运行环境进行隔离,从而导致由于环境污染所产生的 bug 层出不穷,而且各不相同,虽然能一一解决,但始终无法抽象出统一的解决模式,导致开发效率极低,且效果不好。
- 综上背景,LEGO 前端团队最终设计并实现了基于 IFRAME 的 MICRO COMPONENT,不仅完美隔离了两个版本的 LEGO 的运行环境,而且对 LEGO 3.0 基本零侵入,及其优雅,仅需要短短几行配置,就能实现业务逻辑。
设计思想¶
- 为什么需要隔离的运行环境,如何做到
LEGO 3.0 对全局对象 (Global Object)的高度依赖及造成的污染,决定了我们需要隔离 LEGO 3.0 的代码,才能从根本上杜绝不可预测的 Bug 的发生;而 IFRAME 具有良好的隔离性,先确定利用 IFRAME 特性保证运行环境的纯净,剩下的就是技术的问题了。
- 怎样在不侵入 LEGO 3.0 业务逻辑的同时,还能保证可靠的通信
绝对的无侵入是不可能的,在 LEGO 3.0 上寄生着一个 SidercarAgent,用来建立与 LEGO 4.0 APP 的通信,通过一套握手协议和超时机制来建立可靠的通信;通信建立完成之后,会注入 SidercarProxy 里的序列化之后的代码,这些代码都是一些无状态函数式代码,不会产生副作用,因此不用担心 LEGO 3.0 业务逻辑被侵入。
- 什么是 sidecar 模式
Sidecar 模式可以将组件独立出去,从而更好地完成隔离和封装,让应用能够把多种组件和技术整合在一起。
Sidecar 附着在主应用上,并且为主应用提供支持能力;Sidecar 还和主应用共享同样的生命周期,其创建与销毁都是和主应用同步进行的。
SidercarProxy 在 LEGO 4.0 主应用里定义,但实际在 LEGO 3.0 运行,过滤解析主函数发来的事件,并代理到实际的目标函数上去。
整体设计图¶
架构图¶
上下文图¶
生命周期¶
容器图¶
核心模块 UML 图¶
核心模块¶
Sidecar¶
SidecarAgent
: 寄生在 iframe 页面上,注入初始业务代码,完成实际通信SidecarProxy
: 提供业务接口
SidecarAgent¶
- reside on iframe
寄生在 LEGO 3.0 的 IFRAME 上,IFRAME 的 targetOrigin 可以是轻量化的 LEGO 3.0 节目/专辑列表页,可以是一个空的只包含 LEGO 3.0 核心模块的 Bridge 页面;在页面加载完成且判断为 iframe 时,才开启通信。
- do actual communicating stuffs
建立与主页面的通信,询问主页面状态,并等待反馈,若无反馈,继续询问直到超时;若有反馈,则设置设置为 Ready;后续有 Trigger 产生的通信都是实际通过 SidecarAgent 完成。
- inject service code
通信建立成功时,会获取业务初始化代码字符串,将代码反序列化后运行;拦截后续的通信数据,过滤并解析,代理到真正的业务上去。
SidecarProxy¶
- encapsulate libary functions
封装一些通用的库函数,序列化,反序列化,Reply 通信等
- serialize & deserialize parameters, replace & revive functions
序列化定义的函数,传入的参数等
- proxy events
代理 Pilot 过来的事件,并解析
- provide services interfaces
提供业务接口,只要实现的这些接口的 SidecarProxy 实例,都会具有以上能力。
Pilot¶
- collect triggers
作为一个 Broker,收集组件初始化时的所有 Trigger 定义,并在被触发时进行广播。
- build virtual communications with SidecarProxy
建立与 SidecarProxy 的逻辑上的通信,实际的通信由 Trigger 完成。
- provide an entrance for Application/Consumer
主程序的入口,暴露一些事件,如 fire, hook 之类的,其他的对于主程序不可见。
Trigger¶
- provide Event filters
定义 Event 的过滤条件,并以此过滤
- convert an Event into an actual communication
将符合过滤条件的 Event,根据 sink 转化成实际的通信,并将序列化后的参数放在 payload 中,发送出去。
- ensure reliable communication
通过此刻 Sidecar 的状态,判断通信是否成功,否则返回失败。
Event¶
- define Event source
定义事件源。
- define Event sink
定义事件的目的地,即 LEGO 3.0 中真实的业务。
- adoptted as an adapter
作为适配器,将初始化时传入的配置,改装成 MICRO COMPONENT 可以识别的事件。
// for example
{
source: {
action: ''
header: {
from: '',
to: ''
},
payload: {
eventName: 'click',
target: '#button1'
}
},
sink: {
action: 'PILOT_TRIGGER'
header: {
from: '',
to: ''
},
payload: {
method: 'dispatchEvent',
eventName: 'click',
target: '#button2'
}
}
}
实例化一个应用¶
MICRO COMPONENT 的核心模块单独打包成 MICRO COMPONENT CORE,提供了必要的核心功能的实现和业务接口,但不包含具体业务的实现;而实现了这些接口的应用,则具备了 MICRO COMPONENT 的能力。
Configurate EventMaps¶
- 配置事件源和事件接收器
MICRO COMPONENT 初始化时,会将配置清单作为参数传入,每一项配置只用关心 source 和 sink;通过 触发 source 里定义的 Event,就能执行 sink 里实际业务的功能。
Implement Pilot¶
- 管理 iframe 的 targeOrigin
- 管理事件配置表
Implement SidecarProxy¶
- 初始化 iframe 页面展示
对于通用的空白 Bridge 页面,需要在此定义一些初始化逻辑。
- 实现 SidecarProxy 提供的事件转发接口,转发到具体业务
比如,对于列表操作区,将触发类型分成三种:dispatchEvent、callFunction 和 jumpLink,在LEGO 3.0 页面会分别被转化成原生事件,直接调用函数和跳转链接。
特性¶
- 对 Lego 3.0 基本零侵入,优雅降级使用旧版功能
- 隔离性强,不关心主应用及 iframe 应用的语言环境
- 轮询策略、状态机模型、超时机制,保证可靠通信
- 可移植,只需要实现 Pilot 及 Sidecar 提供的接口即可
- 良好的封装,使用者只关心业务逻辑,不关心如何实现通信
- 符合配置大于编码的开发原则
落地场景¶
- 操作迁移
- 截图插件迁移
- 预览播放,追剧日历等
Tricks & Optimizations¶
- Lego 3.0 页面轻量化,提升组件初始化速度
- Tip/Message 劫持,错误捕获,统一外观
- 在 SPA 中延长 iframe 生命周期,较少加载次数,优化体验
- 异步加载组件