微前端到底是什么?
時間:2022-08-11 13:00:02 | 來源:網(wǎng)站運營
時間:2022-08-11 13:00:02 來源:網(wǎng)站運營
關(guān)注「前端向后」微信公眾號,你將收獲一系列「用
心原創(chuàng)」的高質(zhì)量技術(shù)文章,主題包括但不限于前端、Node.js以及服務端技術(shù)
本文首發(fā)于 ayqy.net ,原文鏈接:
http://www.ayqy.net/blog/micro-frontends/一.簡介
為了解決龐大的一整塊后端服務帶來的變更與擴展方面的限制,出現(xiàn)了微服務架構(gòu)(Microservices):
微服務是面向服務架構(gòu)(SOA)的一種變體,把應用程序設(shè)計成一系列松耦合的細粒度服務,并通過輕量級的通信協(xié)議組織起來
具體地,將應用構(gòu)建成一組小型服務。這些服務都能夠獨立部署、獨立擴展,每個服務都具有穩(wěn)固的模塊邊界,甚至允許使用不同的編程語言來編寫不同服務,也可以由不同的團隊來管理
然而,越來越重的前端工程也面臨同樣的問題,自然地想到了將微服務思想應用(照搬)到前端,于是有了「微前端(micro-frontends)」的概念:
Micro frontends, An architectural style where independently deliverable frontend applications are composed into a greater whole.
即,一種由獨立交付的多個前端應用組成整體的架構(gòu)風格。具體的,
將前端應用分解成一些更小、更簡單的能夠獨立開發(fā)、測試、部署的小塊,而在用戶看來仍然是內(nèi)聚的單個產(chǎn)品:
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.
二.特點
簡單來講,微前端的理念類似于微服務:
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.
將龐大的整體拆成可控的小塊,并明確它們之間的依賴關(guān)系。關(guān)鍵優(yōu)勢在于:
- 代碼庫更小,更內(nèi)聚、可維護性更高
- 松耦合、自治的團隊可擴展性更好
- 漸進地升級、更新甚至重寫部分前端功能成為了可能
簡單、松耦合的代碼庫
比起一整塊的前端代碼庫,微前端架構(gòu)下的代碼庫傾向于更小/簡單、更容易開發(fā)
此外,更重要的是避免模塊間不合理的隱式耦合造成的復雜度上升。通過界定清晰的應用邊界來降低意外耦合的可能性,增加子應用間邏輯耦合的成本,促使開發(fā)者明確數(shù)據(jù)和事件在應用程序中的流向
增量升級
理想的代碼自然是模塊清晰、依賴明確、易于擴展、便于維護的……然而,實踐中出于各式各樣的原因:
- 歷史項目,祖?zhèn)鞔a
- 交付壓力,當時求快
- 就近就熟,當時求穩(wěn)……
總存在一些不那么理想的代碼:
- 技術(shù)棧落后,甚至強行混用多種技術(shù)棧
- 耦合混亂,不敢動,牽一發(fā)何止動全身
- 重構(gòu)不徹底,重構(gòu)-爛尾,換個姿勢重構(gòu)-又爛尾……
而要對這些代碼進行徹底重構(gòu)的話,
最大的問題是很難有充裕的資源去大刀闊斧地一步到位,在逐步重構(gòu)的同時,既要確保中間版本能夠平滑過渡,同時還要持續(xù)交付新特性:
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.
所以,為了實施漸進式重構(gòu),我們需要一種增量升級的能力,先讓新舊代碼和諧共存,再逐步轉(zhuǎn)化舊代碼,直到整個重構(gòu)完成
這種增量升級的能力意味著我們能夠
對產(chǎn)品功能進行低風險的局部替換,包括升級依賴項、更替架構(gòu)、UI 改版等。另一方面,也帶來了技術(shù)選型上的靈活性,有助于新技術(shù)、新交互模式的實驗性試錯
獨立部署
獨立部署的能力在微前端體系中至關(guān)重要,能夠縮小變更范圍,進而降低相關(guān)風險
因此,每個微前端都應具備有自己的持續(xù)交付流水線(包括構(gòu)建、測試并部署到生產(chǎn)環(huán)境),并且要能獨立部署,不必過多考慮其它代碼庫和交付流水線的當前狀態(tài):
就算舊的系統(tǒng)是按固定周期季度發(fā)布或手動發(fā)布的,甚至隔壁團隊誤發(fā)布了一個半成品或有問題的特性也無關(guān)緊要。也就是說,如果一個微前端已經(jīng)準備好發(fā)布了,它就應該隨時可發(fā)布,并且只由開發(fā)維護它的團隊來定
P.S.甚至還可以結(jié)合BFF 模式實現(xiàn)更進一步的獨立:
團隊自治
除代碼庫及發(fā)布周期上的解耦之外,微前端還有助于形成完全獨立的團隊,由不同團隊各自負責一塊產(chǎn)品功能從構(gòu)思到發(fā)布的整個過程,團隊能夠完全擁有為客戶提供價值所需的一切,從而快速高效地運轉(zhuǎn)
為此,應該圍繞業(yè)務功能縱向組建團隊,而不是基于技術(shù)職能劃分。最簡單的,可以根據(jù)最終用戶所能看到的內(nèi)容來劃分,比如將應用中的每個頁面作為一個微前端,并交給一個團隊全權(quán)負責。與基于技術(shù)職能或橫向關(guān)注點(如樣式、表單、校驗等)組織的團隊相比,這種方式能夠提升團隊工作的凝聚力
三.實現(xiàn)方案
實現(xiàn)上,
關(guān)鍵問題在于:
- 多個 Bundle 如何集成?
- 子應用之間怎樣隔離影響?
- 公共資源如何復用?
- 子應用間怎樣通信?
- 如何測試?
多 Bundle 集成
微前端架構(gòu)中一般會有個
容器應用(container application)將各子應用集成起來,職責如下:
- 渲染公共的頁面元素,比如 header、footer
- 解決橫切關(guān)注點(cross-cutting concerns),如身份驗證和導航
- 將各個微前端整合到一個頁面上,并控制微前端的渲染區(qū)域和時機
集成方式分為 3 類:
- 服務端集成:如 SSR 拼裝模板
- 構(gòu)建時集成:如 Code Splitting
- 運行時集成:如通過 iframe、JS、Web Components 等方式
服務端集成
服務端集成的關(guān)鍵在于
如何保證各部分模板(各個微前端)能夠獨立發(fā)布,必要的話,甚至可以在服務端也建立一套與前端相對應的結(jié)構(gòu):
每個子服務負責渲染并服務于對應的微前端,主服務向各個子服務發(fā)起請求
構(gòu)建時集成
常見的構(gòu)建時集成方式是將子應用發(fā)布成獨立的 npm 包,共同作為主應用的依賴項,構(gòu)建生成一個供部署的 JS Bundle
然而,
構(gòu)建時集成最大的問題是會在發(fā)布階段造成耦合,任何一個子應用有變更,都要整個重新編譯,意味著對于產(chǎn)品局部的小改動也要發(fā)布一個新版本,因此,
不推薦這種方式運行時集成
將集成時機從構(gòu)建時推遲到運行時,就能避免發(fā)布階段的耦合。常見的運行時集成方式有:
- iframe
- JS:比如前端路由
- Web Components
雖然直覺上用 iframe 好像不太好(性能、通信成本等),但在這里確實是個合理選項,因為 iframe 無疑是最簡單的方式,還天然支持樣式隔離以及全局變量隔離
但這種
原生的隔離性,意味著很難把應用的各個部分聯(lián)系到一起,路由控制、歷史棧管理、深度鏈接(deep-linking)、響應式布局等都變得異常復雜,因而限制了 iframe 方案的靈活性
另一種最常見的方式是前端路由,每個子應用暴露出渲染函數(shù),主應用在啟動時加載各個子應用的獨立 Bundle,之后根據(jù)路由規(guī)則渲染相應的子應用。目前看來,是
最靈活的方式還有一種類似的方式是Web Components,將每個子應用封裝成自定義 HTML 元素(而不是前端路由方案中的渲染函數(shù)),以獲得Shadow DOM帶來的樣式隔離等好處
影響隔離
子應用之間,以及子應用與主應用間的樣式、作用域隔離是必須要考慮的問題,常見解決方案如下:
- 樣式隔離:開發(fā)規(guī)范(如BEM)、CSS 預處理(如SASS)、模塊定義(如CSS Module)、用 JS 來寫(CSS-in-JS)、以及shadow DOM特性
- 作用域隔離:各種模塊定義(如ES Module、AMD、Common Module、UMD)
資源復用
資源復用對于 UI 一致性和代碼復用有重要意義,但
并非所有的可復用資源(如組件)都必須在一開始就提出來復用,建議的做法是前期允許一定程度的冗余,各個 Bundle 在各自的代碼庫中創(chuàng)建組件,直到形成相對明確的組件 API 時再建立可供復用的公共組件
另一方面,資源分為以下 3 類:
- 基礎(chǔ)資源:完全不含邏輯功能的圖標、標簽、按鈕等
- UI 組件:含有一定 UI 邏輯的搜索框(如自動完成)、表格(如排序、篩選、分頁)等
- 業(yè)務組件:含有業(yè)務邏輯
其中,
不建議跨子應用復用業(yè)務組件,因為會造成高度耦合,增加變更成本
對于公共資源的歸屬和管理,一般有兩種模式:
- 公共資源歸屬于所有人,即沒有明確歸屬
- 公共資源歸集中管理,由專人負責
從實踐經(jīng)驗來看,前者很容易衍變成沒有明確規(guī)范,且背離技術(shù)愿景的大雜燴,而后者會造成資源創(chuàng)建和使用的脫節(jié),比較推薦的模式是
開源軟件的管理模式:
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.
即,所有人都能補充公共資源,但要有人(或一個團隊)負責監(jiān)管,以保證質(zhì)量、一致性以及正確性
應用間通信
通過自定義事件間接通信是一種避免直接耦合的常用方式,此外,React 的單向數(shù)據(jù)流模型也能讓依賴關(guān)系更加明確,對應到微前端中,從容器應用向子應用傳遞數(shù)據(jù)與回調(diào)函數(shù)
另外,路由參數(shù)除了能用于分享、書簽等場景外,也可以作為一種通信手段,并且具有諸多優(yōu)勢:
- 其結(jié)構(gòu)遵從定義明確的開放標準
- 頁面級共享,能夠全局訪問
- 長度限制促使只傳遞必要的少量數(shù)據(jù)
- 面向用戶的,有助于依照領(lǐng)域建模
- 聲明式的,語義上更通用("this is where we are", rather than "please do this thing")
- 迫使子應用之間間接通信,而不直接依賴對方
但原則上,無論采用哪種方式,都應該
盡可能減少子應用間的通信,以避免大量弱依賴造成的強耦合
測試
每個子應用都應該有自己的全套測試方案,特殊之處在于,除單元測試、功能測試外,還要有
集成測試:
- 集成測試:保證子應用間集成的正確性,比如跨子應用的交互操作
- 功能測試:保證頁面組裝的正確性
- 單元測試:保證底層業(yè)務邏輯和渲染邏輯的正確性
自下而上形成一個金字塔結(jié)構(gòu),每一層只需驗證在其下層覆蓋不到的部分即可
四.示例
- 在線 Demo:https://demo.microfrontends.com/
- 源碼地址:micro-frontends-demo/container
- 詳細介紹:The example in detail
五.缺點
當然,這種架構(gòu)模式并非百益而無一害,一些問題也隨之而來:
- 導致依賴項冗余,增加用戶的流量負擔
- 團隊自治程度的增加,可能會破壞協(xié)作
流量負擔
獨立構(gòu)建意味著公共資源的冗余,繼而增加用戶的流量負擔
沒有非常理想的解決辦法,一種簡單的方案是將公共依賴從(子應用的)構(gòu)建產(chǎn)物中剔除,但又會引入構(gòu)建時耦合:
Now there is an implicit contract between them which says, “we all must use these exact versions of these dependencies”.
操作/管理上的復雜性
在采用微前端之前,先要考慮幾個問題:
- 現(xiàn)有的前端開發(fā)、測試、發(fā)布流程如何擴展支持很多個應用?
- 分散的,控制弱化的工具體系及開發(fā)實踐是否可靠?
- 針對各式各樣的前端代碼庫,如何建立質(zhì)量標準?
總之,與之前不同的是,微前端將產(chǎn)生一堆小的東西,因此需要考慮
是否具備采用這種方法所需的技術(shù)和組織成熟度六.總結(jié)
類似于微服務之于后端,前端業(yè)務在發(fā)展到一定規(guī)模之后,也需要一種用來分解復雜度的架構(gòu)模式,于是出現(xiàn)了
微服務思想在前端領(lǐng)域的應用,即微前端。主要目的在于:
- 技術(shù)架構(gòu)上進一步的擴展性(模塊邊界清晰、依賴明確)
- 團隊組織上的自治權(quán)
- 開發(fā)流程上能獨立開發(fā)、獨立交付
最大的意義在于解鎖了
多技術(shù)棧并存的能力,尤其適用于漸進式重構(gòu)中架構(gòu)升級過渡期:
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).
允許低成本嘗試新技術(shù)棧,甚至允許選用最合適的技術(shù)棧做不同的事情(類似于微服務中允許用不同的語言編寫不同服務):
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.
參考資料
- Micro Frontends
- I don’t understand micro-frontends.
- Micro Frontends
- Micro frontends—a microservice approach to front-end web development