关注「前端向后」微信公众号,你将收获一系列「用心原创」的高质量技术文章,主题包括但不限于前端、Node.js以及服务端技术css
本文首发于 ayqy.net ,原文连接:http://www.ayqy.net/blog/micr...html
为了解决庞大的一整块后端服务带来的变动与扩展方面的限制,出现了微服务架构(Microservices):前端
微服务是面向服务架构(SOA)的一种变体,把应用程序设计成一系列松耦合的细粒度服务,并经过轻量级的通讯协议组织起来具体地,将应用构建成一组小型服务。这些服务都可以独立部署、独立扩展,每一个服务都具备稳固的模块边界,甚至容许使用不一样的编程语言来编写不一样服务,也能够由不一样的团队来管理git
然而,愈来愈重的前端工程也面临一样的问题,天然地想到了将微服务思想应用(照搬)到前端,因而有了 微前端(micro-frontends)的概念:github
Micro frontends, An architectural style where independently deliverable frontend applications are composed into a greater whole.
即,一种由独立交付的多个前端应用组成总体的架构风格。具体的,将前端应用分解成一些更小、更简单的可以独立开发、测试、部署的小块,而在用户看来仍然是内聚的单个产品:web
Decomposing frontend monoliths into smaller, simpler chunks that can be developed, tested and deployed independently, while still appearing to customers as a single cohesive product.
简单来说,微前端的理念相似于微服务:npm
In short, micro frontends are all about slicing up big and scary things into smaller, more manageable pieces, and then being explicit about the dependencies between them.
将庞大的总体拆成可控的小块,并明确它们之间的依赖关系。关键优点在于:编程
比起一整块的前端代码库,微前端架构下的代码库倾向于更小/简单、更容易开发后端
此外,更重要的是避免模块间不合理的隐式耦合形成的复杂度上升。经过界定清晰的应用边界来下降意外耦合的可能性,增长子应用间逻辑耦合的成本,促使开发者明确数据和事件在应用程序中的流向sass
理想的代码天然是模块清晰、依赖明确、易于扩展、便于维护的……然而,实践中出于各式各样的缘由:
总存在一些不那么理想的代码:
而要对这些代码进行完全重构的话,最大的问题是很难有充裕的资源去大刀阔斧地一步到位,在逐步重构的同时,既要确保中间版本可以平滑过渡,同时还要持续交付新特性:
In order to avoid the perils of a full rewrite, we'd much prefer to strangle the old application piece by piece, and in the meantime continue to deliver new features to our customers without being weighed down by the monolith.
因此,为了实施渐进式重构,咱们须要一种增量升级的能力,先让新旧代码和谐共存,再逐步转化旧代码,直到整个重构完成
这种增量升级的能力意味着咱们可以对产品功能进行低风险的局部替换,包括升级依赖项、更替架构、UI 改版等。另外一方面,也带来了技术选型上的灵活性,有助于新技术、新交互模式的实验性试错
独立部署的能力在微前端体系中相当重要,可以缩小变动范围,进而下降相关风险
所以,每一个微前端都应具有有本身的持续交付流水线(包括构建、测试并部署到生产环境),而且要能独立部署,没必要过多考虑其它代码库和交付流水线的当前状态:
就算旧的系统是按固定周期季度发布或手动发布的,甚至隔壁团队误发布了一个半成品或有问题的特性也可有可无。也就是说,若是一个微前端已经准备好发布了,它就应该随时可发布,而且只由开发维护它的团队来定
P.S.甚至还能够结合BFF 模式实现更进一步的独立:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-emzJdqoY-1575946169113)(https://martinfowler.com/arti...]
除代码库及发布周期上的解耦以外,微前端还有助于造成彻底独立的团队,由不一样团队各自负责一块产品功能从构思到发布的整个过程,团队可以彻底拥有为客户提供价值所需的一切,从而快速高效地运转
为此,应该围绕业务功能纵向组建团队,而不是基于技术职能划分。最简单的,能够根据最终用户所能看到的内容来划分,好比将应用中的每一个页面做为一个微前端,并交给一个团队全权负责。与基于技术职能或横向关注点(如样式、表单、校验等)组织的团队相比,这种方式可以提高团队工做的凝聚力
实现上,关键问题在于:
微前端架构中通常会有个容器应用(container application)将各子应用集成起来,职责以下:
集成方式分为 3 类:
服务端集成的关键在于如何保证各部分模板(各个微前端)可以独立发布,必要的话,甚至能够在服务端也创建一套与前端相对应的结构:
每一个子服务负责渲染并服务于对应的微前端,主服务向各个子服务发起请求
常见的构建时集成方式是将子应用发布成独立的 npm 包,共同做为主应用的依赖项,构建生成一个供部署的 JS Bundle
然而,构建时集成最大的问题是会在发布阶段形成耦合,任何一个子应用有变动,都要整个从新编译,意味着对于产品局部的小改动也要发布一个新版本,所以,不推荐这种方式
将集成时机从构建时推迟到运行时,就能避免发布阶段的耦合。常见的运行时集成方式有:
虽然直觉上用 iframe 好像不太好(性能、通讯成本等),但在这里确实是个合理选项,由于 iframe 无疑是最简单的方式,还自然支持样式隔离以及全局变量隔离
但这种原生的隔离性,意味着很难把应用的各个部分联系到一块儿,路由控制、历史栈管理、深度连接(deep-linking)、响应式布局等都变得异常复杂,于是限制了 iframe 方案的灵活性
另外一种最多见的方式是前端路由,每一个子应用暴露出渲染函数,主应用在启动时加载各个子应用的独立 Bundle,以后根据路由规则渲染相应的子应用。目前看来,是最灵活的方式
还有一种相似的方式是Web Components,将每一个子应用封装成自定义 HTML 元素(而不是前端路由方案中的渲染函数),以得到Shadow DOM带来的样式隔离等好处
子应用之间,以及子应用与主应用间的样式、做用域隔离是必需要考虑的问题,常看法决方案以下:
资源复用对于 UI 一致性和代码复用有重要意义,但并不是全部的可复用资源(如组件)都必须在一开始就提出来复用,建议的作法是前期容许必定程度的冗余,各个 Bundle 在各自的代码库中建立组件,直到造成相对明确的组件 API 时再创建可供复用的公共组件
另外一方面,资源分为如下 3 类:
其中,不建议跨子应用复用业务组件,由于会形成高度耦合,增长变动成本
对于公共资源的归属和管理,通常有两种模式:
从实践经验来看,前者很容易衍变成没有明确规范,且背离技术愿景的大杂烩,然后者会形成资源建立和使用的脱节,比较推荐的模式是开源软件的管理模式:
Anyone can contribute to the library, but there is a custodian (a person or a team) who is responsible for ensuring the quality, consistency, and validity of those contributions.
即,全部人都能补充公共资源,但要有人(或一个团队)负责监管,以保证质量、一致性以及正确性
经过自定义事件间接通讯是一种避免直接耦合的经常使用方式,此外,React 的单向数据流模型也能让依赖关系更加明确,对应到微前端中,从容器应用向子应用传递数据与回调函数
另外,路由参数除了能用于分享、书签等场景外,也能够做为一种通讯手段,而且具备诸多优点:
但原则上,不管采用哪一种方式,都应该尽量减小子应用间的通讯,以免大量弱依赖形成的强耦合
每一个子应用都应该有本身的全套测试方案,特殊之处在于,除单元测试、功能测试外,还要有集成测试:
自下而上造成一个金字塔结构,每一层只需验证在其下层覆盖不到的部分便可
固然,这种架构模式并不是百益而无一害,一些问题也随之而来:
独立构建意味着公共资源的冗余,继而增长用户的流量负担
没有很是理想的解决办法,一种简单的方案是将公共依赖从(子应用的)构建产物中剔除,但又会引入构建时耦合:
Now there is an implicit contract between them which says, “we all must use these exact versions of these dependencies”.
在采用微前端以前,先要考虑几个问题:
总之,与以前不一样的是,微前端将产生一堆小的东西,所以须要考虑是否具有采用这种方法所需的技术和组织成熟度
相似于微服务之于后端,前端业务在发展到必定规模以后,也须要一种用来分解复杂度的架构模式,因而出现了微服务思想在前端领域的应用,即微前端。主要目的在于:
最大的意义在于解锁了多技术栈并存的能力,尤为适用于渐进式重构中架构升级过渡期:
Suddenly we are not tightly coupled with one stack only, we can refactor legacy projects supporting the previous stack and a new one that slowly but steadily kicks into production environment without the need of a big bang releases (see strangler pattern).
容许低成本尝试新技术栈,甚至容许选用最合适的技术栈作不一样的事情(相似于微服务中容许用不一样的语言编写不一样服务):
we can use different version of the same library or framework in production without affecting the entire application, we can try new frameworks or approaches seeing real performances in action, we can hire the best people from multiple communities and many other advantages.