Skip to content

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 运行,过滤解析主函数发来的事件,并代理到实际的目标函数上去。

整体设计图

架构图

上下文图

context

生命周期

x

容器图

container

核心模块 UML 图

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 生命周期,较少加载次数,优化体验
  • 异步加载组件

找找画图灵感

如何画架构图