好的前端开发很难。扩展前端开发,使许多团队可以同时处理大型复杂产品,这变得更加困难。在本文中,我们将描述将前端整体拆分成许多更小,更易管理的片段的最新趋势,以及该体系结构如何提高…

                                                                                                                                                                                    好的前端开发很难。扩展前端开发,使许多团队可以同时处理大型复杂产品,这变得更加困难。在本文中,我们将描述将前端整体拆分成许多更小,更易管理的片段的最新趋势,以及该体系结构如何提高处理前端代码的团队的效率和效率。在讨论各种收益和成本的同时,我们还将介绍一些可用的实现选项,并且将深入研究一个演示该技术的完整示例应用程序。 

近年来,微服务已迅速普及,许多组织都使用这种架构风格来避免大型,整体后端的局限性。尽管有关构建服务器端软件这种风格的文章已很多,但许多公司仍在与整体式前端代码库作斗争。
也许您想构建一个渐进式或响应式Web应用程序,但是找不到一个轻松的地方来开始将这些功能集成到现有代码中。也许您想开始使用新的JavaScript语言功能(或可以编译为JavaScript的多种语言之一),但是您无法在现有的构建过程中使用必要的构建工具。或者,也许您只是想扩展您的开发,以便多个团队可以同时处理一个产品,但是现有整体中的耦合和复杂性意味着每个人都在互相踩脚。这些都是真正的问题,都会对您有效地向客户提供高质量体验的能力产生负面影响。
最近,我们看到越来越多的注意力集中在复杂的现代Web开发所必需的总体体系结构和组织结构上。特别是,我们看到了将前端整体分解为更小,更简单的块的模式,这些块可以独立开发,测试和部署,同时仍然对客户而言是一个具有凝聚力的产品。我们称这种技术为微前端,我们将其定义为:

在ThoughtWorks技术雷达的2016年11月号中,我们列出了微前端作为组织应评估的一种技术。后来我们将其推广到试用版,最后推广到采用,这意味着我们认为它是一��行之有效的方法,应在合理的情况下使用。

Test
图1:微前端已经多次出现在技术雷达上。

我们从微前端看到的一些主要好处是:

较小,更紧密和可维护的代码库
解耦的自主团队可扩展性更高的组织
能够以比以前更多的增量方式升级,更新甚至重写前端的功能

这些头条新闻优势与微服务可以提供的某些优势并非偶然。
当然,涉及软件体系结构时不会有免费的午餐-一切都是有代价的。一些微前端实现可能导致依赖关系重复,从而增加了用户必须下载的字节数。此外,团队自主权的急剧增加可能会导致团队工作方式分散。尽管如此,我们认为可以控制这些风险,而且微前端的收益往往超过成本。

好处

我们没有按照特定的技术方法或实施细节来定义微观前端,而是将重点放在了出现的属性和它们带来的好处上。

增量升级

对于许多组织而言,这是其微前端之旅的开始。过去的技术堆栈或在交付压力下编写的代码阻碍了旧的,大型的前端组件的发展,目前正进行着完全重写的尝试。为了避免完全重写的危险,我们更希望逐个扼杀旧的应用程序,与此同时,继续为我们的客户提供新功能,而不会受到整体功能的影响。
这通常会导致建立微前端架构。一旦一个团队经历了将功能一直投入生产且几乎不对旧世界进行任何修改的经验,其他团队也将希望加入新世界。仍然需要维护现有代码,在某些情况下,继续为其添加新功能可能是有意义的,但是现在可以选择了。
最终的结果是,我们有更大的自由可以对产品的各个部分进行逐案决策,并对我们的体系结构,依赖关系和用户体验进行增量升级。如果我们的主框架发生了重大的重大变化,那么每个微前端都可以在有意义的时候进行升级,而不必被迫停止世界并立即升级所有内容。如果我们想尝试新技术或新的交互方式,则可以比以前更孤立的方式进行。

简单,解耦的代码库

根据定义,每个单独的微前端的源代码都将比单个整体前端的源代码小得多。这些较小的代码库对于开发人员而言更趋于简单和容易。尤其是,我们避免了彼此不了解的组件之间无意和不适当的耦合所引起的复杂性。通过在应用程序的有界上下文周围绘制粗线,我们使这种偶然的耦合变得更加困难。
当然,一个单一的高层体系结构决策(即“让我们去做微前端”)不能替代老式的干净代码。我们并非试图免除自己对代码的思考,并努力提高其质量。相反,我们试图通过艰难地做出错误的决定,而容易做出好的决定来使自己陷入成功的陷阱。例如,跨有限上下文共享域��型��得更加困难,因此开发人员这样做的可能性较小。同样,微前端可以使您明确和审慎地了解数据和事件在应用程序不同部分之间的流动方式,无论如何,这是我们应该做的事情!

独立部署

就像微服务一样,微前端的独立部署能力是关键。这减小了任何给定部署的范围,进而降低了相关的风险。无论前端代码的托管方式或托管位置如何,每个微前端都应具有自己的连续交付管道,该管道将在整个生产过程中对其进行构建,测试和部署。我们应该能够在不考虑其他代码库或管道的当前状态的情况下部署每个微前端。不管旧的整体式设备是否处于固定的,手动的,每季度发布的周期,或者隔壁的团队是否已将半完成或损坏的功能推送到其主分支中,都没有关系。如果给定的微前端准备好投入生产,那么它应该能够进行生产,并且该决定应由构建和维护它的团队来决定。

Test
图2:每个微前端都独立部署到生产中

自治团队

作为将我们的代码库和发布周期解耦的更高阶优势,我们对于拥有完全独立的团队还有很长的路要走,他们可以拥有从构思到生产再到整个产品的一部分。团队可以完全拥有为客户创造价值所需的一切,从而使他们能够快速有效地行动。为此,我们的团队需要围绕业务功能的垂直部分而不是技术能力组成。一种简单的方法是根据最终用户将看到的产品来精简产品,因此每个微前端都封装了应用程序的单个页面,并由一个团队端到端拥有。这比团队围绕技术或“水平”问题(如样式,形式或验证)组成团队时,具有更高的团队凝聚力。

Test
图3:每个应用程序应由一个团队拥有

简而言之

简而言之,微前端就是将大而恐怖的东西切成更小,更易于管理的部分,然后明确地说明它们之间的依赖关系。我们的技术选择,我们的代码库,我们的团队以及我们的发布流程都应该能够彼此独立地运行和发展,而无需过多的协调。

这个例子

想象一下一个网站,客户可以在该网站上订购要交付的食物。从表面上看,这是一个非常简单的概念,但是如果您想做得好,会有很多令人惊讶的细节:

应该有一个登陆页面,客户可以在其中浏览和搜索餐馆。这些餐厅应该可以通过任何数量的属性进行搜索和过滤,包括价格,美食或客户先前订购的内容
每个餐厅都需要有自己的页面,显示其菜单项,并允许客户选择自己想吃的东西,折扣,餐饮优惠和特殊要求
客户应该有一个个人资料页面,他们可以在其中查看其订单历史记录,跟踪交货以及自定义其付款方式

Test
图4:一个食品配送网站可能会有几个相当复杂的页面

每个页面都有足够的复杂性,因此我们可以轻松地为每个页面辩护一个专门的团队,并且每个团队都应该能够独立于所有其他团队而在其页面上工作。他们应该能够开发,测试,部署和维护其代码,而不必担心与其他团队的冲突或协调。但是,我们的客户仍然应该看到一个无缝的网站。
在本文的其余部分中,我们将在需要示例代码或场景的任何地方使用该示例应用程序。

整合方法

鉴于上面的定义相当宽松,可以合理地将许多方法称为微前端。在本节中,我们将显示一些示例并讨论它们的取舍。所有方法都有一个相当自然的架构-通常,应用程序中的每个页面都有一个微前端,并且有一个容器应用程序,该容器可以:

呈现常见的页面元素,例如页眉和页脚
解决认证和导航等跨领域问题
将各种微前端集中到页面上,并告诉每个微前端何时以及在何处进行渲染

Test
图5:您通常可以从页面的视觉结构中得出您的架构

服务器端模板组成

我们从绝对新颖的前端开发方法开始-从多个模板或片段中渲染服务器上的HTML。我们有一个index.html,其中包含所有常见的页面元素,然后使用服务器端包含从片段HTML文件插入特定于页面的内容:

1
2
3
4
5
6
7
8
9
10
  <html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Feed me</title>
</head>
<body>
<h1> Feed me</h1>
<!--# include file="$PAGE.html" -->
</body>
</html>

我们使用Nginx来提供此文件,并$PAGE通过与所请求的URL进行匹配来配置变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  server {
listen 8080;
server_name localhost;

root /usr/share/nginx/html;
index index.html;
ssi on;

# Redirect / to /browse
rewrite ^/$ http://localhost:8080/browse redirect;

# Decide which HTML fragment to insert based on the URL
location /browse {
set $PAGE 'browse';
}
location /order {
set $PAGE 'order';
}
location /profile {
set $PAGE 'profile'
}

# All locations should render through index.html
error_page 404 /index.html;
}

这是相当标准的服务器端组成。我们之所以可以称其为微前端,是因为我们以这样的方式拆分了我们的代码,使得每个代码代表一个独立的领域概念,可以由一个独立的团队交付。此处未显示的是这些HTML文件如何最终存储在Web服务器上,但是假设它们各自具有自己的部署管道,这使我们可以将更改部署到一个页面上而不会影响或考虑其他页面。
为了获得更大的独立性,可以有一个单独的服务器负责渲染和服务每个微前端,其中一个服务器位于前端,向其他服务器发出请求。通过仔细地缓存响应,可以在不影响延迟的情况下完成此操作。

Test
图6:这些服务器中的每一个都可以独立构建和部署

这个例子说明了微前端不是必须是一种新技术,也不必太复杂。只要我们对设计决策如何影响代码库和团队的自治性保持谨慎,无论我们采用何种技术堆栈,我们都可以实现许多相同的收益。

构建时整合

我们有时看到的一种方法是将每个微前端发布为一个包,并让容器应用程序将它们全部作为库依赖项包含在内。这是package.json示例应用程序的容器外观:

1
2
3
4
5
6
7
8
9
10
  {
"name": "@feed-me/container",
"version": "1.0.0",
"description": "A food delivery web app",
"dependencies": {
"@feed-me/browse-restaurants": "^1.2.3",
"@feed-me/order-food": "^4.5.6",
"@feed-me/user-profile": "^7.8.9"
}
}

起初,这似乎是有道理的。像往常一样,它会产生一个可部署的Javascript捆绑包,从而使我们能够从各种应用程序中删除常见的依赖项。但是,这种方法意味着我们必须重新编译并发布每个微前端,才能发布对产品任何单个部分的更改。就像微服务一样,我们已经看到了如此棘手的发布过程所引起的痛苦,因此我们强烈建议不要使用这种微前端方法。
解决了将我们的应用程序划分为可以独立开发和测试的离散代码库的所有麻烦,让我们不要在发布阶段重新引入所有这些耦合。我们应该找到一种在运行时而不是构建时集成微前端的方法。

通过iframe进行运行时集成

不起眼的iframe是在浏览器中将应用程序组合在一起的最简单方法之一。从本质上讲,iframe可以轻松地从独立的子页面中构建页面。在样式和全局变量互不干扰方面,它们还提供了很好的隔离度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  <html>
<head>
<title>Feed me!</title>
</head>
<body>
<h1>Welcome to Feed me!</h1>

<iframe id="micro-frontend-container"></iframe>

<script type="text/javascript">
const microFrontendsByRoute = {
'/': 'https://browse.example.com/index.html',
'/order-food': 'https://order.example.com/index.html',
'/user-profile': 'https://profile.example.com/index.html',
};

const iframe = document.getElementById('micro-frontend-container');
iframe.src = microFrontendsByRoute[window.location.pathname];
</script>
</body>
</html>

就像使用服务器端include选项一样,从iframe中构建页面并不是一项新技术,也许似乎并不那么令人兴奋。但是,如果我们重新审视前面列出的微前端的主要优势,则只要我们谨慎地划分应用程序和组建团队的方式,iframe便很适合。
我们经常看到很多人不愿意选择iframe。尽管某些不情愿似乎是由直觉造成的,即iframe有点“讨厌”,但人们还是有一些很好的理由让人们避免使用它们。上面提到的容易隔离确实会使它们不如其他选项灵活。在应用程序的不同部分之间建立集成可能很困难,因此它们会使路由,历史记录和深层链接变得更加复杂,并且给使页面完全响应带来了一些额外的挑战。

通过JavaScript运行时集成

我们将描述的下一种方法可能是最灵活的一种,也是我们看到的团队采用频率最高的一种方法。每个微前端都使用