[译]采用微前端架构

1,629 阅读10分钟

考虑到第一篇关于微架构的文章收到的热烈反馈,以及对我们在DAZN推行微前端的方案的诸多提问,我决定就此做一些分享。

在这篇文章中,我将覆盖到微前端架构多种可能的实现方式之一。
尽管微前端对我们的前端应用还是一种很新的模式,很多公司试图去拥抱微前端背后的原则,并且创建了许多解决他们自己遇到的前端和组织问题的挑战的实现方案。 我认为,在开始讲述我们是如何设计我们的实现方案前,需要介绍一下一些其他公司的方案。这并不是详尽的列表,但是意识到有用的不同的可能性,也是很有趣的:

Spotify利用iframes把相同视图的不同部分拼接起来,以在他们的桌面应用中实行微前端。iframes之间通过event bus进行通讯,实现了优雅的解耦,使得应用的不同部分之间即使在不知道消息或事件的监听者的情况下,也可以进行通讯。
同时,这种做法也节省了管理应用内存的大量时间。因为每一次当我们修改iframe的地址的时候,所有的对象都会准备好被自动垃圾回收。

IKEA则使用了一种不同的方式来实现微前端架构。他们在使用的Edge Side Includes(ESI)和Client Side Includes(CSI)的结合。因为这种技术在Gustaf的这篇文章里有详细阐述,因此我不想过多去介绍。但是这绝对是另外一个动态生成页面内容和缓存的机会,缓存结果可以放在CDN或者用户端,这取决于我们想采取哪种方式。

OpenComponents是一个很有意思的框架,并被如Skyscanner或OpenTable这样的公司所使用的。OpenComponents是一个基金的框架,它利用了端到端组件的概念(前端和后端在一起),提交注册并用于构建应用。 此外,在这种情况下,我们能在OpenComponents project website找到大量信息。

在以上三种实现方式中,我们能找到中大规模的组织在创建独立并且与技术无关的微前端方案时的相似之处,虽然有一些不同。值得一提的还有,在这种思想中,Zalando或BuzzFeed等其他贡献者所做出的贡献。

如果我们想总结到现在为止的微前端实现方案,我们能列出3中不同的方式:

  • 使用iframe结合event bus
  • 使用ESI,可以选择是否与CSI
  • 使用OpenComponents或相似的运行时/编译时模板系统

DAZN 的方式

正如我在文章开始所提及的,还有一种需要讨论的方式:DAZN使用的微前端方案。DAZN是一个OTT服务,在多个国家提供直播和付费点播服务。我们的应用不仅存在于web端、移动端,甚至包括智能电视、机顶盒和控制台。之所以要强调这一点,因为我们经常会遇到独特的挑战,我们必须要思考出开箱即用的方案来解决这些问题。

通常,当我们开始一个微前端项目的时候,我们应该问我们自己一些问题。应对挑战的答案,也关系到我们的决策,例如:

  • 我们是否希望在同一个视图中存在多个微前端?
  • 我们怎么在视图中进行路由跳转?
  • 我们怎么在多个微前端间共享数据?
  • 我们怎么生成微前端?在运行时,还是编译时生成?

让我们来尝试回答这些问题,以理解我们拥抱的方式...

我们是否希望在同一个视图中有多个微前端?
不,我们想要每次加载一个微前端,这样我们不需要在多个微前端之间共享的依赖,每一个微前端都足够小,但是又不会太小,我们对最终的结果也会有完整的把控。这是技术上独立并且有良好封装的方式。
基于此,我们能在不影响其他微前端的前提下,采用不同版本的相同框架,甚至采用其他的技术来构建微前端,也不会对整个系统产生任何影响。
我们遵从了领域驱动设计(DDD)的时间来切分子域,使他们真正的映射到产品团队上,并在一个大型组织内创造了由产品人员+前端开发者+后端开发者+人工QA+测试开发组成的垂直体系。这对在大型企业内,与工作效率不同的多个团队一起快速行动起来,是非常有效的。

要牢记于心的是,不同于我们通常认为的,用户并不会完整的使用我们的应用。例如,当用户鉴权通过后,并不会加载关于登录/注册的微前端的所有代码和依赖——因为我们只加载需认证部分的微前端的代码和依赖。
与此同时,如果用户没有通过鉴权,我们并不能百分之百确定他会完成登录流程并成功访问到应用的认证部分。检查一下关于用户如何与应用进行交互的统计数据,如果没有统计数据,你需要投入足够数量的时间,利用Google Analytics, Sentry, LogRocket以及诸如此类的工具来构建合适的观察体系。 记住,微前端极大有利于帮助我们达到如下目标——只加载用户需要的部分,而不加载其他部分。

我们怎么在页面进行路由跳转,以及怎么在微前端之间共享数据? 我们有好几种方式能够达到这个目标,在后端,在边缘,或者在客户端。我们选择在客户端创建一个叫Bootstrap的编排,它实现了4个主要目标:

  • 在微前端之间进行路有导航
  • 加载和卸载微前端(每一次加载/卸载一个,不多个同时执行)
  • 初始化获取配置信息的应用
  • 暴露在微前端之间共享数据的API层

我们怎么生成微前端?在运行时,还是编译时生成? 我们倾向于对我们作品的输出有很高的可预测性,并希望它们能够像SPA一样可以高度缓存,因此我们没有采取在运行时创建一切的方式,我们倾向于在编译时生成微前端,把它们存储在AWS S3上,及通过Cloudfront CDN提供服务。 通过这种方式,我们不需要去担心如何去扩展我们的基础架构,以及在应用服务时的不可预测的边缘情况。我们能在应用部署到生产环境前进行端到端测试和性能测试,这样在应用上线以前,我们对我们部署的应用将会更加有信心。

架构

在我们的案例中,我们决定基于对用户如何与我们的web应用进行交互的提前研究,来将应用拆分成多个子域。对于新开发的项目,我建议联合UX和产品团队,去深入理解用户是怎么去与应用交互的,并遵从领域驱动设计来定义子域和子域关联的上下文边界。 对于DAZN应用,几乎所有的子域在技术上都是单页应用,但是也有一些例外。例如,由于该子域的范围很广,因此视频播放器只是一个组件,然后这些组件和其他的库一样,被引入到微前端当中。

多个微前端通过bootstrap来进行加载和编排,这是一个嵌入在主HTML页面中的简小的JavaScript应用,它基于深度链接的请求、用户状态或已经加载过的微前端中发出的请求,来加载不同的微前端。

bootstrap在应用的生命周期内始终是可用的,它负责加载我们的多个微前端,并在设备和微前端之间暴露了一个极小的抽象层。 当我们将目标定位在多种设备而不仅仅是web浏览器时,上述的细节就会显得更加重要。我们在很多智能电视、机顶盒和控制台上提供我们的应用,这些设备通常有不同的需求和要遵循的I/O API, 这些都可以在booststrap的层面进行封装。

通过这种方式,不需要去改一行代码,我们就可以在多种设备上运行微前端。因为boostrap抽象了微前端运行的平台。

我们可以这样去总结应用是如何在浏览器中加载的:

  1. 用户在浏览器中输入我们的域名,请求我们的web应用
  2. boostrap开始提供服务
  3. bootstrap初始化应用,从API层获取配置信息
  4. 基于初始状态和用户请求(深度链接或默认的URL),来加载正确的微前端
  5. 用户开始使用并喜欢我们基于微前端的web应用

要牢记于心的是,每一个微前端都是独立的,因此我们不在微前端之间共享组件或逻辑。如果你认为这是一个浪费时间和精力的事情,你就不会相信每个团队都因为这个决定而获得了多大的独立性。

根据我们过去所了解到的,代码重复并不是一个差的实践。跨团队的依赖和代码抽象有太多的风险,远比创建3或者4次相同组件,更加充满风险和令人厌烦。我们已经发觉,使用正确数量的时间去分析用户使用的流程以及确定子域,有助于引导我们生成少得多的重复工作。

同时,我们注意到通过使用微前端,由于在分析项目上的初始努力和有意义的子域的创建,我们达到了比预期少得多的重复工作。

如果在你遇到的情况中,这是一个绝对要复用的组件,有一种减少重复的方式是使用web components来标准化组件代码。通过这种技术,它可以结合任何框架进行复用,但是这就需要另外写一篇文章来讨论了。

当我们开始踏上微前端的路程,对我而言非常清楚的是,我不仅要解决技术方面的问题,还必须要思考开发团队的未来。

使用微前端,我们可以在不影响交付速度的前提下,提供我所期望的独立性。每一个团队拥有一个特定的端到端的域,这保证了可以很轻易的去添加新功能,修复bug和进行改进,而不至于对应用的其余部分或多个研发中心之间共享的依赖有致命性影响的风险。

在多次和新加入公司的开发者分享这些信息,以及演讲或在线的workshop,我知道你们会有许许多多关于bootstrap的问题,比如它是怎么去加载微前端,怎么去加载数据,诸如此类。

我将会在下篇文章中回答这些问题。下篇文章将集中在介绍bootstrap上,因此请关注我,以避免错过深入探索微前端世界的机会。

原文地址

Adopting a Micro-frontends architecture, luca mezzalira