Skip to content

微前端的架构和挑战

该笔记学习自书籍《微前端设计与实现》--卢卡.梅扎利拉

应用微前端需要考虑的因素

  • 业务领域描述
  • 自治代码库
  • 独立部署
  • 单一团队负责制

我们面临许多挑战,比如如何在微前端之间进行通信,或者如何将用户从一个视图导向另一个视图,其中最重要的是如何确定微前端的规模。

微前端决策框架

微前端决策框架,它由四个关键性决策组成。

  • 微前端在架构中的定义
  • 微前端的组合
  • 微前端的路由
  • 微前端之间的通信

定义微前端(横向拆分与纵向拆分)

我们需要确定如何从技术视角考虑微前端,并且需要决定是在同一个视图中集成多个微前端,还是每个视图只集成一个微前端

  • 在横向拆分方案中,同一个视图上会有多个微前端。这些组成部分由多个团队负责,需要他们协同工作。这种方法提供了很大的灵活性,我们甚至可以在不同的视图中复用微前端。但是,这样做需要更强的规则约束和管理,以避免微前端在同一项目中被滥用。
  • 在纵向拆分方案中,每个团队负责一个业务领域,比如身份认证或目录体验。在这种情况下,领域驱动设计就会派上用场。通常,我们很少在前端架构上应用领域驱动设计原则,但在这种情况下,我们有充分的理由去探究如何应用这个原则。

微前端的领域驱动设计

领域驱动设计是一种软件开发方法,其开发工作重点是编写领域模型,它要求对某一领域的流程和规则有充分的理解。在前端应用领域驱动设计与在后端应用略有不同。尽管有些概念对构建成功的微前端架构非常有帮助,但有些概念绝不能直接拿来套用。比如,Netflix 的核心域是视频流,核心域内的子域是目录、注册功能和视频播放器。一共有以下三种子域类型。

  • 核心子域:核心子域解释了一个应用之所以存在的主要原因。在组织中,核心子域应该被视为“第一公民”,因为它们提供的价值高于一切。视频目录就是 Netflix 的核心子域。
  • 支持性子域:支持性子域与核心子域相关,但它起的作用不会导致差异化。支持性子域可以支持核心子域,但在为用户提供真正价值方面,它并不是必需的。Netflix 视频的投票系统就是一个例子。
  • 通用子域:通用子域用于完成整个平台开发。通常情况下,一些公司会决定使用现成的软件来处理和主要领域并不紧密相关的事务。比如,对于 Netflix 来说,支付管理与核心子域(目录)无关。但是,因为支付管理可以访问已认证的部分,所以它也是平台的关键部分。

领域驱动设计中的另一个重要术语是限界上下文(bounded context):一个隐藏了实现细节的逻辑边界,暴露 API 以获取模型层的数据。限界上下文将域和子域所定义的业务问题转化为逻辑问题,从而定义数据模型、代码结构,甚至团队

很多时候,我们把时间花在确定技术方案上,却没有考虑业务方面的需求

康威定律指出:“任何组织设计的系统架构都不可避免地反映了该组织的沟通结构。”然而,为了减少摩擦并且更快地实现最终目标,即得到一个令客户满意的优秀产品,这些公司需要让团队结构具备足够的灵活性以适应组织的最佳解决方案。而逆康威策略建议改进团队和组织结构,以实现你所期望的架构。

但一定要记住,并不是所有用户会话都能访问平台的所有 URL。因此,前期的一些调研工作有助于提供更好的用户体验。

微前端组合

客户端组合

在客户端组合的情况下,App shell 自己加载微前端。微前端应该有一个 JavaScript 文件或 HTML 文件作为入口文件。如果是 HTML 文件,则 App shell 可以在 DOM 中动态追加节点,或者使用JavaScript 文件初始化 JavaScript 应用。

我们还可以使用 iframe 的组合来加载不同的微前端,或者在客户端使用一种叫作 Client- Side Include 的嵌入(transclusion)技术。客户端对组件进行懒加载,然后用组件替换占位符标签。比如,一个名为 h-include 的库使用占位符标签,同时创建 AJAX 请求,并将元素的内部 HTML 替换为响应内容。

边缘侧组合

在边缘侧组合中,我们在 CDN 层对视图进行组装。许多 CDN 提供商为我们提供了一种基于 XML 的标记语言,它被称为 Edge Side Includes(ESI)

但 ESI 的一个缺点是每个CDN 提供商的实现方式并不相同。因此,如果采用多 CDN 策略,当我们的代码从一个提供商迁移到另一个提供商时,这种迁移可能会导致大量的代码重构和需要实现的新逻辑。

服务器端组合

最后一种选择是服务器端组合,它可以在运行时或编译时进行组合。在这种情况下,源服务器通过查询所有不同的微前端以生成视图,并组装成最终的页面。如果页面是高度可缓存的,可以让 CDN 为其提供较长的存活时间(time to live,TTL)。但如果页面是根据每个用户进行个性化定制的,在有很多客户端请求时,我们需要认真考虑解决方案的可扩展性。

微前端路由

这个决策与项目中使用的微前端组合机制紧密相关。我们可以选择在源服务器、边缘侧或客户端进行页面路由

详细介绍一下客户端路由:

在这种情况下,可以根据用户的状态来加载微前端。如果用户已经登录,就加载需要登录才能访问的区域,而如果用户是第一次访问应用,就只加载一个登录页面。如果使用一个 App shell 为单页应用加载微前端,这个 shell 负责实现路由逻辑,即先获取路由配置,然后决定加载哪个微前端。这一方法尤其适用于复杂的路由,比如需要基于身份认证、地理定位或任何其他复杂的逻辑来加载微前端的时候。

如果你的团队有更强的前端技能,强烈建议使用客户端路由,这样基于后端配置使用客户端路由就自然而然了。

微前端通信

请记住,每个微前端都不应该知晓同一页面上的其他微前端,否则,我们就违反了独立部署的原则。

在这种情况下,有几种技术选型可以通知其他微前端有事件发生。我们可以在每个微前端中注入事件总线(eventbus),这种机制通过在总线上发送事件让解耦的组件之间能够相互通信,一个微前端发送的事件会通知到每个微前端。如果某个微前端对所发送的事件感兴趣,那么它可以监听事件的触发并做出反应和处理。

另一个解决方案是使用自定义事件。

登录微前端要向认证微前端传递一个 token。如何将 token 从一个微前端传递到另一个微前端呢?我们有几种选择。

  • 我们可以使用类似 Web Storage 的 SessionStorage、LocalStorage 或 cookie(图 3-6)。在这种情况下,我们可能会使用本地存储来独立保存和查询 token。只要微前端在同一个子域中,Web Storage 始终是可用和可访问的状态。
  • 另一种选择是通过查询字符串传递数据。使用查询字符串并不是传递敏感数据(如密码和用户 ID)最安全的方式。