本文要点
微前端可以帮助开发人员专注于功能需求和业务需求。
微前端可以加快产品上市的时间,提高公司内部的重用率,并能简化开发过程。
可能的代价是额外的复杂性、集成成本以及对用户体验和性能的负面影响。
为微前端构建平台与为微服务构建平台非常相似。
Mashroom Server 可以作为此类平台的集成组件。
一个你可能很熟悉的场景是:业务部门雇佣了一些开发人员或外包公司来快速搭建一个新的小型网站销售产品。在某个时候,他们将网站移交给了 IT 部门,这就是困难所在。通常,IT 人员会对业务人员感到愤怒,因为他们委托他人来构建这个新站点,而没有事先征求他们的意见。现在,他们不得不把它整合到他们自己的系统中。很有可能它使用了一些在 IT 领域还没有人使用过的技术,并且它肯定没有遵从已有的领域模型和流程。
但为什么业务人员一开始就绕过了 IT 部门呢?也许他们对自己能够按照官方途径获得所需的东西没有多少信心。有时,这样的请求常常会引起繁琐的讨论,即该需求是否适合整个架构和现有的应用程序环境。也许他们只知道 IT 部门已经完全超负荷了,不可能在这么短的时间内得到他们想要的东西。
一种解决此问题的方法是:为这些小型的微前端(Microfrontend)应用程序定义最小的需求集,而无需考虑构建和部署独立应用程序所需的开销。为了实现这一点,需要一个微前端平台来提供标准化的部署,并与现有流程和站点集成。
本文将介绍如何构建一个微前端平台,并介绍它可能带来的好处以及可能遇到的困难。
原因
当然,微前端并不是万能的,你需要有充分的理由来接受额外的复杂性和集成成本。除了引言中介绍的原因外,还可能有如下的其他原因:
新雇佣的开发人员需要在没有任何入职培训的情况下立即提高生产力——最好使用他们喜欢和习惯了的技术。
你的业务应用程序往往可以使用5年以上,但对于一个可能存在不了太久的Web框架,你希望减轻对它的依赖。
你的公司已经投资了一些商业系统,比如CMS、CRM或 Portal ,但是你希望内部开发的应用程序在系统将要被替换时,不会变得没用。另外,你还需要在那里集成现有的应用程序。
你将要开发一个巨大的Web应用程序,而它根本无法作为一个单体应用来维护(至少不能长期维护)。
你需要向不再维护(或者没有对应的开发人员)的遗留应用程序中添加新特性。
什么是微前端?
因为“微前端”(“microfrontend”)是当前的流行语,根据本文的上下文,我将阐明一下这个术语的含义。
可以在网上找到的一些定义是:
“一种架构风格,将可独立交付的前端应用程序组成了一个更大的整体。” ( Cam Jackson的”微前端“)
“[…]将网站或Web应用程序看作是由独立团队所拥有的功能组合。[…]团队是跨职能的,并能端到端地开发其对应的功能,[…]( Michael Geers的“微前端:将微服务思想扩展到前端开发”)
这些都不是什么新想法了。通过超链接集成的小型 Web 应用程序并构建站点的概念(仍然)非常流行。过去也有很多通过较小的独立构建块呈现页面的概念,例如 Java Portlets 。
即使是现在使用”微前端“这个术语来指代现代 JavaScript 应用程序,也是有多种可能的方法的。因此,本文中使用的”微前端“,指的是这样的一个应用程序:
本质上是一个JavaScript富客户端(例如SPA或Web组件),它在任意DOM节点中 独立 运行,并且尽可能小且性能最佳。
不安装全局库、字体或样式。
对其所嵌入的网站不作任何假定;特别是它不假定任何现有路径,因此资产和API的所有基本路径都必须是可配置的。
具有一个定义良好的接口,由启动配置和一些运行时消息(事件)组成。
应该是可实例化的(这样它就可以在同一个页面上通过使用不同配置存在多次)。
理想情况下,从站点继承共享样式,并且只提供定义其布局所必需的样式。
这些技术要求导致其具有非常特殊的特性:
这种应用程序 几乎可以在任何地方运行 。你只需提供一个小的集成层就可以配置和启动它。你甚至可以将其打包为移动应用程序,或者通过Web View将其集成到富客户端中。
它的使用非常灵活,因为行为可配置,并且(理想情况下)可以适配宿主站点的样式。
完全没有厂商锁定——至少在使用主流框架创建SPA或Web组件时是这样。你不必坚持使用该框架——完全可以在下一个微前端中使用某个不同的框架,甚至可以重写现有的框架。 (这应该很容易,因为它们很小。)
在给定的约束条件下,开发人员可以在每个新的微前端中调整其所使用的框架和工具(因此,糟糕的设计决策可能不会产生很大的影响)。
由于微前端往往不依赖于其托管的站点,因此它们可以完全独立开发,就像开发任何常规的SPA一样。
就业务需求而言,这意味着什么呢?
你可以定义满足特定业务需求的特定微前端,例如客户搜索、客户详情和客户产品,然后可以在不同的业务上下文中重用这些微前端(例如,新业务、维护流程、客户服务流程等)。
你可以为特定于用户的仪表板、流程流组合几个微前端,以便支持不同的业务视图和业务流程。
微前端集成
微前端的集成可以在不同层面上实现:
在构建时,这会导致运行时的单体化,并且不允许独立部署。
在服务器端,使用一些模板引擎(例如,使用 Mosaic),该模板引擎允许独立部署,但在运行时中不加载(和卸载)。
在客户端的运行时,这是一种最强大的方法,也是我将要在这里讨论的方法。
从纯技术的角度来看,客户端集成其实并不复杂。假设捆绑的微前端公开了一些全局启动函数,那么只需加载捆绑并调用该启动函数即可:
但很快一些问题就出现了:
customerId
来自哪里?也许它应该在运行时从其他微前端(例如搜索)中传递过来。但是要怎么做呢?showDeleteButton
可能取决于一些安全角色,但是它们来自哪里呢?我们如何将其映射到这种细粒度的权限上呢?我们如何访问客户端可能无法访问的后端API呢?
我们在哪里存储微前端的实例配置呢?
为了解决这些问题以及类似的问题,我们已经为微前端开发了一个集成平台,并在不久前将其开源了: Mashroom Server。
要将现有的微前端集成到 Mashroom 中,只需向 package.json
中添加一些元数据,并使用具有特定签名的 bootstrap 方法即可。服务可以注册本地 NPM 包或微前端可以运行在公开 /package.json
的远程服务器上。
对于上面的示例,你只需要将其添加到 package.json
即可:
全局 bootstrap 方法:
如你所见, bootstrap 方法不仅仅需要一个简单的配置对象。 portalAppSetup
还包含了用户信息和 permissions
对象。 clientServices
用于为诸如消息总线之类的常见服务注入处理程序,消息总线可通过发布/订阅模式交换消息。这些服务的使用完全是可选的。
下面是一个使用不同框架编写的多个微前端的示例页面:
使用 Kubernetes 和 Mashroom Server 构建平台
首先,我们需要确定部署单元(Docker 镜像)的组成:
微前端的资源(JS、CSS、资产)
用于交付资源的HTTP服务器
用于简化与实际后端(BFF,用于前端的后端)通信的API,通常是REST API
可能还包含一个允许部署单元独立调用微前端的HTML页面或模板
在 Kubernetes 上部署和操作此类单元与部署和操作微服务并没有太大的区别,可以应用相同的最佳实践。所以,我不打算在这里详细讨论了。事实上,如果你已经有了一个微服务平台,那么你也可以把它用于微前端。
缺少的构建块是允许将单独部署的微前端集成到页面或舱室的组件。此组件需要具有你希望从任何 Portal 中获取的所有特性:
身份验证和基于角色的授权
定义站点和页面的方法
主题的支持
但它也需要一组与微前端方法非常相关的特性:
微前端注册和扫描Kubernetes集群以查找新部署的能力。
一种通过微前端(静态或动态)组成页面的方法。
自动代理API调用,以便微前端可以触达 Portal 基于的后端(在大多数情况下,不能从集群外部直接访问)。
一种使微前端可以访问其静态资产(例如图像)的方法。
能够在页面上(甚至更高的地方)建立微前端间通信的能力。
这就是 Mashroom Server 发挥作用的地方。它支持所有这些需求,并且具有非常灵活的插件体系结构以及许多现有的用于身份验证、持久性、消息传递和监控的插件。
因此,该平台可能如下所示:
Microfrontend Proxy 使遗留系统可以获取微前端资源并连接到平台上的 API。当然微前端也可以使用公共服务。
我们提供了一个带有脚本的 GitHub仓库,在几分钟内即可在 Google Kubernetes Engine上建立一个这样的平台。它也能很容易地适用于其他 Kubernetes 平台。
经验教训
构建 Mashroom Server 并在项目中使用它,为考虑采用微前端架构提供了技术和非技术方面的深入指导。
组织架构
与构建微服务类似,关键是让 DevOps 团队尽可能独立地构建和操作他们的微前端。请记住,他们的目标是尽快将有价值的东西交付给业务人员。为了使他们能够在平台和集成复杂性方面高效快速地工作,应该有一个 平台团队( Platform Team) 来支持他们。平台团队对平台负责,并对每件事都有充分的指导,以便新的微前端能够顺利集成。理想情况下,DevOps 团队可以在不打扰平台团队的情况下计划、开发和发布一个新的微前端。此外,他们应该能够自己访问日志并自己监控操作问题。否则,该方法将无法扩展,并且平台团队将会超负荷。
这里是一个简化了的组织架构图:
图中缺少的是 治理(Governance) 。特别是在大规模使用这种方法时,你需要进行一些监督,以防止不受控制的增长,并最大程度地重用现有的微前端。如果你已经有很多微服务或 WebService 目录,那么你应该在这方面执行非常类似的操作。
用户体验
结果表明,在使用微前端构建系统时,用户界面设计(User Interface Design)和用户体验(User Experience)是最关键的部分。这是因为具有不同颜色、字体甚至不同行为的完全异构的 UI 很难被业务人员和用户所接受。
所以,我推荐以下方法:
确保开发人员明白,一个微前端除了微前端特有的样式外,绝不应该包含任何样式。
同时,向他们提供可以在开发过程中应用的CSS(一个主题),以便他们可以看到集成后的外观效果。
用纯CSS(或SASS,或LESS,或任何你喜欢的方式)定义所有常见的UI组件。这样,团队仍然可以使用他们选择的JavaScript框架。他们只需要使用预定义的CSS类。
确保你有一个富有表现力的UI指南,它不仅涵盖单个UI元素,还包括最重要的交互模式。
正如你将要看到的那样, style 和 UX 引入了团队间的耦合 ,但这是无可避免的。基于 CSS 的本质,你可以认为这种耦合实际上非常弱。
与业务人员讨论
当你与业务人员讨论此方法及其优缺点时,你应该强调指出,微前端使他们可以将讨论重点放在所需的业务功能上。在开始实施之前,需要讨论的非功能性需求会更少。
你还可以提及的其他优点包括:
这种方法使得IT部门更有可能找到开发人员,因为他们不需要特定的框架技能或了解现有的系统知识(只要你的后端API有充分的文档说明),就可以迅速开始实施。
一段时间后,你将拥有一个现有的构建块目录,这些构建块能加速新UI的开发。例如,如果你已经有一个显示某些基本客户数据的微前端,那么你就不必再实现该部分了,从而可以节省一些时间和成本。
尽管行为和“外观”需要有很大的不同,但可主题化和可配置的微前端往往可以为不同的销售渠道和子公司所重用。
微前端的粒度
你会经常听到的这样的问题:“你如何 切割 微前端?”在这里,你将看到它与微服务的一些区别,你会在微服务中尝试识别领域模型的边界并防止职责重叠。相反,在微前端领域,可重用性和可伸缩性是主要考虑的问题,有一些冗余并不是什么大问题。
另一个主要的区别是由谁来决策:由于微服务隐藏在后台,并覆盖了通用业务流程,因此决策由企业架构师或软件架构师做出。相反,微前端活跃在用户空间中,由 UI 设计人员和 UX 专家(用户体验专家)负责。
由于 UI 设计人员通常使用可重用的构建块,他们可能已经在不知不觉中识别出了微前端。在与业务人员交流时,它们甚至被命名为特有的名称。因此,从现有的设计来看,微前端是自然而然出现的。这也是为什么微前端方法需要高度关注 UI/UX 的另一个原因。
确保 UI 设计人员了解现有的微前端,以便他们将其视为一个固定的构建块,而不会引入任何更改。这也会促使整个公司的 UI 更加标准化。
如果你在决策如何分解现有设计时遇到了困难,下面这些问题可能会对你有所帮助:
哪些构建块可以在其他应用程序中重用?
作为一个独立的应用程序,哪些构建块更有意义?
如果你对业务人员提及某个构建块的名称,他们能想象出来吗?
一旦你建立了一个微前端平台,你还将看到一种从后到前的方法,即团队开始实现的微服务视图,甚至不知道它们是否会被使用以及将在何处使用。这也是一种有效的方法,但是你可以期待,一旦这些视图被实际集成到某个地方,就需要进行一些翻新。
快速入门模板
正如我前面所说的,即使需要为部署、UI/UX 和集成制定规则,在开发新的微前端时,还是有很高的自由度。所以,大多数团队都会对一些结构和起点心存感激。
提供几个微前端模板项目来加快速度是有意义的。它们不仅应该涵盖实际的源代码,还应该涵盖将要交付资源的服务端(它也可以是一个用于前端的后端)、Dockerfile 以及所有必要的构建和部署管道。基本上,所有这些都是为了尽快将微前端交付生产所必需的。
这也减少了必要书面指导的数量。而且,仅通过提供一个基于某些常见主流 UI 框架的模板项目,它就可以减少在微前端中使用的不同框架的数量。由于大多数团队只会使用它(同样,他们不必这样做),这将导致代码库会非常同质,这通常可以降低使用每个现有框架所带来的风险(IT 经理会将不胜感激)。
安全性
当然,安全性有几个层次和方面。我们在此将要讨论的主要是身份验证和授权。对于其他的 XSS、SQL 注入、DOS 攻击等,微前端平台与其他 Web 应用程序没有什么不同,也必须加以保护。
因此,安全性至关重要,但也很复杂,大公司往往具有不同的身份验证和授权策略以及多个身份提供者。理想情况下,微前端不必理会它们(开发人员也不必理会)。
由托管微前端的 Web 应用程序进行身份验证是有意义的。还有一些基本的授权,比如检查用户是否有权查看某个页面或该页面上的微前端。
但是,对于微前端内部的细粒度权限又如何呢?这里,一种好的方法是为你的微前端定义抽象权限。基本上就是像 deleteCustomer 或 assignDiscount 这样的密钥。然后,宿主应用程序负责在启动配置时传递权限对象,如下所示:
在你的微前端中,如果 permission.deleteCustomer 为 false,则可以隐藏删除按钮。
这种方法将确定正确权限的责任推给了宿主应用程序。 Mashroom 支持这种开箱即用的方式:如果你将权限密钥和用户角色之间的映射添加到 package.json
中的插件定义中。权限将自动确定并传递。这样的定义如下所示:
这里真正有趣的是:如果你的微前端不处理访问令牌或角色,它甚至可以集成到一些带有奇怪的、过时的角色概念的遗留系统中。集成层只需以某种方式从它们中确定权限即可。
但当然,基于某些 JSON 属性禁用按钮并不十分安全。用户可以轻松地操作 DOM 并触发删除请求。所以,还需要在后端进行检查。基本上有如下两种可能性:
宿主应用程序在将请求转发到实际后端时添加访问令牌。因此,后端可以根据自己的规则检查权限。
每个微前端都有自己的BFF(Backend-For-Frontend,用于前端的后端),它只是基于相同的权限密钥在服务端重复检查。
第一种方法很难在异构环境中实现。如果宿主应用程序是某个遗留系统或存在多个身份提供者,那么就很难生成访问令牌。
我们通常使用第二种方法,并通过服务帐户(这是许多公司的唯一方式)访问实际的后端系统。 Mashroom 可以通过 HTTP 头自动将计算出的权限转发给 BFF,因此开发人员可以简单地根据报文头中的权限密钥检查来自微前端的请求。
该方法如下所示(这里可以用任何其他宿主应用程序替换 Portal ):
微前端通信
有趣的是,当我与开发人员讨论微前端时,首先要考虑的问题之一就是通信。它们如何互相交谈的呢?
但根据我的经验,它们交谈不多。许多年前,我们在 Java Portlet 项目中也看到了同样的情况。即使它们交谈,也不应该说太多,因为传递大数据结构意味着紧密的耦合,而你要避免这种情况。
因此,我推荐如下方法:
使用轻量级的发布/订阅(publish/subscribe)机制进行通信( Mashroom 具有一个内置的前端消息总线)。
只传递很小的JSON消息,例如,一些状态信息或密钥。要抵制使用通信来共享状态的冲动,比如一些客户数据,因为通常情况下,整个公司都没有统一的模型。只传递客户ID,即使这意味着在同一页面上必须多次加载同一数据。
共享库
共享库的问题将会在两个层面上出现:
你可以考虑创建一个具有通用UI组件或通用BFF功能的库,并在开发团队中共享它。
在浏览器运行时中共享库可能是有意义的,例如UI框架库,因此不是每个微前端都必须自己发布库。
要小心第一点。从软件工程的角度来看,它是有意义的,但是它引入了不同团队之间的耦合和依赖。你将会看到团队彼此等待,并且平台团队可能会成为瓶颈。如果你开发的是通用库,那么将是否使用它设置为可选(开发人员应该将它们仅仅看作是一种要约),这样你不会失去从头开始的能力。
但是,为在 CSS 中定义的 UI 组件创建一个 参考实现 是绝对有意义的。开发人员可以直接使用它,如果使用不同的 JavaScript 框架,也可以将其用作模板。此外,这样的库可以作为展示,这对团队新成员而言非常有用。
关于第二点:如今,大多数 SPA 库的占用空间都非常小(即使是 Angular ,这要归功于 AOT 编译)。因此,多次加载运行时(对于页面上的一些微前端)并不是什么大问题。但这也不是一件好事。如果你需要大量的其他库或无法避免使用庞大的 UI 组件库,那么肯定会对性能产生负面影响。在这种情况下,你可以考虑使用 webpack 的 Web DLL 机制或 Mashroom DLL插件。但这只有在所有(或至少大多数)开发团队都同意用相同的框架版本或某种依赖包的情况下才有效——这又引入了某种耦合。
性能
我们很少看到仅仅是由于微前端方法而引起的性能问题,但是这些问题可能会成倍增加。因此,要确保每个人都在努力制作小且性能良好的 SPA,并尽量减少需要加载的资产数量。
另外你可以:
确保每个人在使用一些富UI组件库(这时组件参考实现就派上用场了)之前都能三思而后行。
同意使用图标字体(除非公司没有提供)。
继续使用Web DLL作为一个选项(参见上文)。
由于微前端方法可能导致不同级别的过度抓取,即多次加载相同的数据,因此最好在客户端或服务端添加一些缓存机制。
未解问题
像你通常在 SPA 中使用路由的一样,微前端的路由也有点问题。微前端如何在页面上“共享”浏览器的 URL?这里最简单的方法就是禁止任何 URL 操作。如果丢失浏览器导航并不是什么大不了的事的话(对于大多数习惯于企业应用程序的用户来说,这似乎并不算什么),这种方法是有效的。 Mashroom 还提供了一个将状态编码到 URL 中的服务,但这会导致与宿主应用程序的耦合,这并不理想。因此,可能有必要在这里定义一种新的模式,以明确微前端是如何共享 URL 空间的。
另外,为微前端调整服务注册模式也很有用。 Mashroom 有一个内置的注册表,但是最好使用某些外部服务,比如 Eureka。向每个微前端中添加元数据来描述其功能,将会使其用例远远超出静态排列的微前端:
不需要放置特定的微前端,只需描述所需的功能,宿主应用程序就可以动态地查找最佳候选项。
你可以创建完全动态的舱室,它会随着用户希望查看数据的增多而增长。系统只需查找能够显示或编辑某些数据类型的微前端并加载它即可。
你可以分析客户的电子邮件(或基本上可以是任何任务),并向用户呈现一个定制的页面,其中包含完成该任务所需的所有内容。
你可以使用元数据为同一页面上的不同用户组选择不同的微前端。 (可用于金丝雀发布或A/B测试。)
总结
明确地说,构建一个微前端平台是一项巨大的努力。不仅要从技术角度来看,还要从组织角度来看,因为你必须包括业务人员、UI/UX 专家以及 IT 部门中的每个人。每个参与人员都需要了解基本概念及其约束和好处。因此,这不仅是一个架构上的决策,而且是一个需要说服很多人的转型过程。
此外,微前端方法本身(即使在没有成熟平台的最小规模的情况下)也具有一些折衷:你必须在集成方面投入额外的努力,以获得无缝的用户体验并防止性能问题。你也不得不解决在一些单体应用程序中从未遇到过的问题(例如,路由)。
另一方面,它解决了许多问题,特别是大公司的 IT 部门难以解决的问题:无法快速响应新的业务想法和需求。由相同的 UI 功能需要使用不同技术多次实现带来的高成本。需要对开发人员进行长时间的培训直到他们能够提高工作效率的复杂系统。以及对做出错误的框架决策的持续恐惧,这最终会阻碍创新。
最重要的是,微前端方法可以帮助你隐藏 IT 领域的非功能复杂性,并使你可以专注于业务需求。
作者介绍:
Jürgen Kofler 是一名软件开发人员和 Web 爱好者。他热衷于解决复杂性问题,并将乐趣带回企业开发中。在过去的几年中,他专注于用更小的独立构建块(无论是 Portlet、Widget 还是 Microfrontend)构建站点的策略。目前,他供职于维也纳的一个大型微前端平台。
原文链接:
https://www.infoq.com/articles/microfrontends-business-needs/
评论