采用微前端架构
考虑到关于微前端的第一篇文章的大量反馈,以及我们在 DAZN 采用的方式收到的问题,我决定分享更多有关这个话题的内容。
在这篇文章中,我将一一介绍微前端架构各种可能的实现。
尽管微前端是我们前端应用的新模型,但许多公司都已经试图接受它们背后的原则,并且已经缔造了多种实现方式,来解决它们的前端和组织上的挑战。
在开始考虑我们如何设计我们的实现之前,我认为值得一提的有一些方式,这不是一个详尽的列表,但有趣的是,它们可以被用来了解可用的不同可能性:
- Spotify
在桌面端中使用微前端,利用 iframe 将同一视图的不同部分拼接在一起。
iframe 之间的通信是通过事件总线进行的,该事件总线很好地分离了应用的不同部分,允许它们在不知道谁将要收听消息或事件的情况下进行通信。
此外,这种方法可以节省大量的时间来管理应用内存,因为每次我们更改 iframe 位置时,所有对象都可以自动进行垃圾回收。
- 宜家
决定采用不同方式实施微前端,他们使用的是 Edge Side Includes(ESI)与 Client Side Includes(CSI)混合,我不想在这个技术上赘述,因为它在Gustaf的帖子中被广泛提及,不过,它绝对是另一种动态生成我们页面内容,并在 CDN 级别或客户端缓存结果的机会,这取决于我们想采取的方法。
- OpenComponents
它是 Skyscanner、OpenTable 等几家公司使用的有意思的框架。
OpenComponents 是一个以自我为中心(opinionated)的框架,它利用端到端组件(前端+后端)的概念,把这些组件提交给一个寄存器,然后用来组合一个应用。
还有,在这种情况下,我们可以在 OpenComponents 项目网站上找到更多的信息。
在这 3 个实现之间,我们可以找到相似的地方,但其中的一些差异,会被中大型规模的组织用来创建独立和技术不可知的微前端。这方面值得一提的是 Zalando 或 BuzzFeed 也是作为这个思想流派中的另一种贡献者。
如果我们想总结一下到目前为止讨论的实现,我们可以列出 3 种不同的方法:
。使用iframes + 事件总线
。使用 ESI 结合(或不结合)CSI
。使用 OpenComponents 或类似的运行时/编译时模板系统
“DAZN 采用的方式”
正如我在本文开头所提到的,还有另一个实现要讨论:DAZN 采用的方法。
DAZN 是一种 OTT 服务,可在多个国家/地区提供实时和点播内容。我们的应用不仅可以在网络和移动设备上使用,还可以在智能电视,机顶盒和控制台上使用,这一点非常重要,因为我们经常遇到独特的挑战,我们需要开箱即用地来解决它们。
通常,当我们开始一个微前端项目时,我们应该问自己几个问题,并基于与我们的决策面临的相关挑战的答案,例如:
- 我们想在同一视图中使用多个微前端吗?
- 我们如何在页面之间切换路由?
- 我们如何在微前端之间共享数据?
- 我们如何生成微前端?运行时还是编译时?
让我们尝试回答所有的这些问题,以便理解我们采用的方法......
我们想在同一个视图中使用多个微前端吗?
不,我们希望每次加载 1 个微前端,这样我们就没有微前端之间的共享依赖关系,每个微前端都足够小但不太小,我们完全控制最终的结果,它是独立的技术和良好的封装。
我们可以使用同一框架的不同版本,而不会影响其他微框架甚至不同的技术,从而不会对整个应用产生任何影响。
我们遵循领域驱动设计(DDD)实践来切割我们的子域,并使它们真正独立地映射产品团队结构,并在由产品人员+前端开发人员+后端开发人员+功能测试+测试开发组成的大型组织内创建垂直,这对于快速迭代非常有用,在大公司需要时,团队之间的速度会有所不同。
请记住,比你想象的更频繁,我们的应用并非完全地被用户使用,例如,当用户通过身份验证时,将不会加载登录/注册微前端的所有代码和依赖项,因为我们只加载经过验证的区域的微前端。
同时,当用户未经过身份验证时,并不是 100% 确定她将完成登录的流程,并成功访问应用的经过身份验证的区域,请检查你的用户与你的用户交互方式的统计信息应用,如果你没有使用它们,请使用 Google Analytics,Sentry,LogRocket 等工具投入适当的时间来创建正确的可观察性。
请记住,微前端的目标是帮助实现加载仅仅是用户需要的 而不是更多。
我们如何在页面之间切换路由?如何在微前端之间共享数据?
我们可以通过多种方式在后端,边缘或客户端实现这一点。我们选择客户端创建一个名为 Bootstrap 的 协调器(orchestrator),它有 4 个主要目标:
- 微前端之间的路由
- 加载和卸载微前端(每次 1 个,从不多个)
- 初始化检索配置的应用
- 公开 API 层以在微前端之间共享数据
我们如何生成微观前端?运行时或编译时间?
我们喜欢我们用的人工制品的结果非常可预测,我们希望它们像 SPA 一样高度安全,因此我们没有采取在运行时创建任何东西的路径,但我们比较喜欢在编译时生成微前端,把它们存储在 AWS S3 上,并通过 Cloudfront CDN 提供服务。
通过这种方式,我们不必担心在我们为应用提供服务时扩展我们的基础架构的问题或发生不可预测的边缘情况,我们可以在部署生产环境之前运行端到端测试和性能测试,从而在上线之前对我们部署的内容更有信心。
架构
在我们的案例中,我们决定将应用拆分为多个子域,以便提前研究用户如何与我们的 Web 应用进行交互。对于绿地(green-field)项目,我建议深入了解你的用户如何与你的 UX 和产品团队一起与应用进行交互,并遵循领域驱动设计用于定义子域及其相关的边界上下文。
对于 DAZN 应用,几乎每个子域在技术上都被转换为单页应用,但也有一些例外,例如,由于该子域的广泛范围,视频播放器是一个组件,然后这些组件被导入到微前端内部和任何其他库一样。
微前端由引导程序加载和协调,引导程序是嵌入在主 HTML 页面中的简单的 vanilla javascript 应用,它根据深层链接(deep-link)来请求加载不同的微前端,用户状态或任意请求都来自加载的微前端。
这就是我们的架构的样子:
引导程序在我们的应用的生命周期中始终可用,它负责加载我们的微前端,并在设备和微前端之间暴露一层微小的抽象。
当你的目标是多个设备而不仅仅是浏览器时,也就是我们在许多智能电视,机顶盒和控制台上都可以使用我们的应用时,这个细节变得更加相关,它们通常都有不同的要求和I/O API ,他们被顺延封装在引导程序级别。
通过这种方式,我们可以在多个设备中运行微前端而无需更改一行代码,因为引导程序正在抽象运行微前端的平台。
如果我们想总结应用在浏览器中的加载方式,我们可以说:
- 用户在浏览器中输入我们的域名请求我们的 Web 应用
- 提供引导程序
- 引导程序初始化应用,并从 API 层检索一些配置
- 基于初始状态和用户请求(深层链接或默认 URL)加载正确的微前端
- 用户沉浸在我们基于微前端的Web应用中!
请记住,每个微前端都是独立的,因此我们不会在微前端之间共享组件或逻辑。
如果你认为这是浪费时间和精力,你就无法想象每个团队在这个决策中获得了多少独立性。
代码重复并不总是一种糟糕的做法,正如我们过去所了解的那样,通常跨团队依赖和代码抽象风险比创建相同组件的 3 到 4 倍更危险和繁琐。
我们注意到,花费适当的时间来分析用户流并识别子域可以减少重复次数。
此外,我们注意到使用微前端的话,由于最初分析项目并创建有意义的子域,因此跨团队的依赖关系并不像其他项目那样经常发生。
如果在你的情况下,它是绝对必须重用的组件,有一种方法可以使用 Web 组件来减轻重复,以标准化组件代码,使用这种技术,它可以与任何框架结合使用,但这个讨论应该在另一篇帖子里😉。
当我们开始迈向微前端时,对我来说非常清楚的是必须考虑开发团队的未来,而不仅仅是解决技术问题。
借助微前端,我们能够在不影响交付速度的情况下提供我所寻求的独立性,每个团队都拥有端到端的特定域,保证了添加新功能,修复错误或添加改进的简单方法,没有冒着对我们的多个开发中心传播的其他应用或依赖关系产生影响的风险。
与新开发人员加入公司以及在我的会谈或在线研讨会期间多次分享这些信息后,我知道你可能会有大量关于引导程序的问题,它如何加载微前端,如何共享数据等等。
我将在下一篇文章中回答所有这些问题,这些问题将集中在引导程序上,所以请跟随我,不要错过微前端世界中的深潜。
如果你对微型前端有任何好奇或疑问,请随时与我联系,我总是热衷于尽可能多地帮助社区😁!