近年来,微服务大受欢迎。越来越多的组织开始使用这种类型的架构来避免大型单体的限制。
在本文中,我们将描述一种将“前端单体”分解为更小、更易于管理的部分的趋势。以及这种架构如何提高跨团队的效率。
图1
图 1 显示了一个应用程序,其中前端由一个“前端整体”组成,后端由多个微服务组成。
微前端的定义是:微前端的思想是将微服务的概念扩展到前端领域。
微前端的基本思想,是将你的前端拆分为一系列可独立部署且松散耦合的前端应用程序(称为微前端)。然后整合这些微前端以创建单个前端应用程序(参见图 2)。
你可以在每页显示一个微前端,并使用超链接将其连接。也可以在一个页面上显示多个微前端(见图 2)。
图2
为了加快应用程序的开发,将微前端视为Web 应用程序的单个功能业务(单一职责)是可行的。每个“微前端”负责单个业务领域/用例,例如“配置文件”、“目录”、“订单”。它有自己的表现层、服务层(微服务)、持久层和独立的 数据库。从开发的角度来看,每个功能业务由一个团队实施。
为什么这会加快开发过程?每个团队都专注于一个业务领域,因此与其他团队的协调较少。这提高了开发的速度。
下面我将描述一个使用微前端的示例应用程序。
想象一个网站,客户可以在其中订购外卖食品。
首先,你有一个登陆页面,客户可以在其中搜索餐厅(见图 4)。micro-frontend-browse-restaurants。
然后,每家餐厅都有自己的页面,在该页面上显示菜单项,客户可以选择他或她想要订购的东西(见图 3)。micro-frontend-order-food。
最后,客户有一个个人资料页面,他们可以在其中查看他们的订单历史记录、跟踪订单并自定义他们的付款选项。micro-frontend-user-profile。
本文的其余部分都使用此示例应用程序。
图3
此应用程序的架构如下:
每页显示几个微前端。并且有一个container 作为主要入口点(见图 4),主要负责:
图4
“集成方法”的意思是:如何在前端“聚合”微前端?
图 5 显示了三种“集成方法”。
图5
对于构建时集成,我们将每个单独的微前端作为一个包发布。在构建时,这些单独的微前端使用聚合的package.json。
Monorepo刻意用于构建时间集成 。使用 Monorepo,你可以管理多个存储库内的所有代码。库可以由特性、组件、实用程序或 UI 工具包组成。
Monorepo 的一大缺点是我们必须重新编译和发布 Monorepo 中的每个微前端,以在单独的微前端中发布更改!要维护 Monorepo,你可以使用Lerna、Nrwl 或 Angular Workplace。
服务器端集成,是整合多个模板或片段在服务器上呈现 HTML 。这些片段代表微前端。
在下面的示例中,显示了一个index.html,它包含了其他 HTML 文件(参见图 7)。
图7
我们使用Nginx提供此文件(参见图 8),通过匹配请求的 URL 来配置 $PAGE 变量。
因此,如果用户选择 URL /browse,$PAGE 变量将填充 HTML browse 片段。
图8
运行时集成,指的是在运行时聚合和配置前端中的微前端(见图 5)。在这种情况下,package.json不再用于聚合各个微前端。
在下面的示例中,Web 组件用作创建单独微前端的技术。这些 Web 组件也可用于之前的“集成方法”。
简而言之,Web Components 是你可以在 HTML 页面和 Web 应用程序中使用的独立组件。Web Components 也称为自定义元素。
作为开发人员,你可以编写自己的自定义元素,具有自己的 CSS、HTML 和 Javascript。此自定义元素基于 Web 标准,可用于最常用的浏览器。Web Components 是面向未来的,因为它们不依赖于框架或库。因此非常适合作为构建微前端的技术。
在此示例中,我们将通过示例应用程序的“订购食品”页面(参见图 3)自己创建一个 Web 组件。这个 Web 组件的名称是micro-frontend-order-food,在这个例子中(见图 9)有以下参数:>
图9
该 Web 组件的实现如下所示(参见图 10)。为了让这个例子简单,菜单项被省略了。
对于这个 Web 组件,我们首先定义一个扩展 HTMLElement的类。
使用HTMLElement,你可以创建自定义 HTML 元素。在构造函数中,首先调用 super(),这意味着 HTMLElement 的所有逻辑都可以用来构建 Web Component。接下来,我们将shadow DOM附加到 Web 组件。shadow DOM 是一个独立的 DOM(或“视图”),用于显示此 Web 组件的某些内容。
我们使用document.createElement(img)实例化所需的图像,并使用传递的参数设置 alt 和 src 属性:>
图10
最后,定义了一个新的自定义元素/Web 组件:
customElements.define(‘ micro-frontend-order-food’, MicroFrontendOrderFood)
这个 Web 组件称为micro-frontend-order-food。我们可以在 HTML 页面中使用这些来显示带有文本的图像。
图 11 中显示了一个 index.html,它模拟了我们的示例应用程序(订购食物)。
这里的 index.html代表容器应用程序,它负责微前端的路由和渲染。在顶部,我们的微前端包含在<script> 标签中。刚刚讨论的micro-frontend-order-food定义在 JavaScript 包中:https://order.example.com/bundle.js
所述<div id=”micro-frontend-root”>是在所选择的微前端呈现占位符。常量webComponentsByRoute包含一个查找表,用于在你选择路由时确定要呈现的 Web 组件/微前端。
常量webComponentType包含基于所选路由的实际所选微前端,通过:window.location.pathname
图11
我们使用document.createElement (web ComponentType)实例化选定的微前端。最后,它链接到占位符:<div id=”micro-frontend-root”>。这是通过root.appendChild(webComponent) 完成的。
上面的示例虽然是一个简单示例,但它演示了 Runtime 集成方法。
在下图(图 12)中,你可以推断出你可以在哪种情况下使用哪种集成方法。
对于小型和/或不复杂的应用程序(其中 1 或 2 个团队正在处理),你可以忽略集成方法而只假设前端单体。
图12
一个 UI 组件库由一系列 UI 构建块组成,例如输入元素、列表、标签栏和网格等。
你可以选择每个微前端都有自己的 UI 组件库(见图 13)。这样做的缺点是代码重复,并且 UI 组件的样式和操作的一致性可能会降低。
为了更加一致,我们还可以应用一个通用的UI 组件库。这样做的缺点是微前端然后通过这个库链接。如果你选择通用 UI 组件,请确保它仅包含UI 逻辑,不包含业务逻辑。
图13
关于微前端最常见的问题之一是如何让它们相互通信。通常建议微前端尽可能少地进行通信,因为这会引入我们首先试图避免的不需要的链接。
也就是说,微前端之间通常需要一定量的通信。自定义事件允许微前端间接通信。可以使用“事件构造函数”创建事件,如下所示:New Event(build)(参见图 14)。
例如,dispatchEvent可以启动微前端X发布一个“构建”事件。然后微前端 Y监听此事件(使用addEventListener方法)并进行进一步处理。
图14
微前端就是将大型 Web 应用程序分解为Verticals。这样以后,我们的技术选择、我们的代码库、我们的团队和我们的发布流程 (CI/CD) 理想情况下都可以彼此独立地工作,而无需过度与其他团队协作。这种架构也有一个缺点。这里我们提几点:
如果你想对整个 Web 应用程序进行更改,则需要对其他各个团队实施的各个微前端(和微服务)进行更改。
对于整个 Web 应用程序的集成测试,你必须启动许多不同的应用程序和服务器。难点在于测试微前端之间的依赖关系和通信。
独立构建的微前端可以包含重复的代码。重复的代码也意味着更多的维护、更多的出错机会以及 UI 组件的样式和操作的一致性降低。
此外,在许多实际案例中,微前端具有优势。随着时间的推移,Spotify 或宜家等大型组织已经成功地将微前端应用于旧代码的改造。借助微前端,这些公司可以更快地响应市场变化并提供推动其客户体验。