在過(guò)去的幾個(gè)星期里,隨著 Martin Fowler 博客上那篇 Cam Jackson 寫(xiě)的微前端的文章發(fā)布惶室,到處都在討論 Microfrontend温自。作為一個(gè)微前端 “專(zhuān)家”,我也分享一下:如何去落地微前端皇钞。
微前端是一種類(lèi)似于微服務(wù)的架構(gòu)悼泌,它將微服務(wù)的理念應(yīng)用于瀏覽器端,即將單頁(yè)面前端應(yīng)用由單一的單體應(yīng)用轉(zhuǎn)變?yōu)槎鄠€(gè)小型前端應(yīng)用聚合為一的應(yīng)用夹界。各個(gè)前端應(yīng)用還可以獨(dú)立開(kāi)發(fā)馆里、獨(dú)立部署。同時(shí)掉盅,它們也可以在共享組件的同時(shí)進(jìn)行并行開(kāi)發(fā)——這些組件可以通過(guò) NPM 或者 Git Tag也拜、Git Submodule 來(lái)管理。
為什么需要微前端趾痘?
微前端不是銀彈慢哈,它和微服務(wù)一樣會(huì)帶來(lái)大量的挑戰(zhàn)。
- 遺留系統(tǒng)遷移永票。解決遺留系統(tǒng)卵贱,才是人們采用微前端方案最重要的原因
- 聚合前端應(yīng)用。微服務(wù)架構(gòu)侣集,可以解耦后端服務(wù)間依賴(lài)键俱。而微前端,則關(guān)注于聚合前端應(yīng)用世分。
- 熱鬧驅(qū)動(dòng)開(kāi)發(fā)编振。新的技術(shù),既然很熱鬧臭埋,那么就學(xué)吧踪央。
微前端的實(shí)現(xiàn),意味著對(duì)前端應(yīng)用的拆分瓢阴。拆分應(yīng)用的目的畅蹂,并不只是為了架構(gòu)上好看,還為了提升開(kāi)發(fā)效率荣恐。
為此液斜,微前端帶來(lái)這么一系列的好處:
1.應(yīng)用自治累贤。只需要遵循統(tǒng)一的接口規(guī)范或者框架,以便于系統(tǒng)集成到一起少漆,相互之間是不存在依賴(lài)關(guān)系的臼膏。
2.單一職責(zé)。每個(gè)前端應(yīng)用可以只關(guān)注于自己所需要完成的功能检疫。
3.技術(shù)棧無(wú)關(guān)讶请。你可以使用 Angular 的同時(shí),又可以使用 React 和 Vue屎媳。
除此夺溢,它也有一系列的缺點(diǎn):
- 應(yīng)用的拆分基礎(chǔ)依賴(lài)于基礎(chǔ)設(shè)施的構(gòu)建,一旦大量應(yīng)用依賴(lài)于同一基礎(chǔ)設(shè)施烛谊,那么維護(hù)變成了一個(gè)挑戰(zhàn)风响。
- 拆分的粒度越小,便意味著架構(gòu)變得復(fù)雜丹禀、維護(hù)成本變高状勤。
- 技術(shù)棧一旦多樣化,便意味著技術(shù)椝幔混亂
畢竟沒(méi)有銀彈持搜。
如何設(shè)計(jì)微前端架構(gòu)?
就當(dāng)前而言焙矛,要設(shè)計(jì)出一個(gè)微前端應(yīng)用不是一件容易的事——還沒(méi)有最佳實(shí)踐葫盼。在不同的落地案例里,使用的都是不同的方案村斟。出現(xiàn)這種情況的主要原因是贫导,每個(gè)項(xiàng)目所面臨的情況、所使用的技術(shù)都不盡相同蟆盹。為此孩灯,我們需要了解一些基礎(chǔ)的微前端模式。
架構(gòu)模式
微前端應(yīng)用間的關(guān)系來(lái)看逾滥,分為兩種:基座模式(管理式)峰档、自組織式。分別也對(duì)應(yīng)了兩者不同的架構(gòu)模式:
- 基座模式寨昙。通過(guò)一個(gè)主應(yīng)用面哥,來(lái)管理其它應(yīng)用。設(shè)計(jì)難度小毅待,方便實(shí)踐,但是通用度低归榕。
- 自組織模式尸红。應(yīng)用之間是平等的,不存在相互管理的模式。設(shè)計(jì)難度大外里,不方便實(shí)施怎爵,但是通用度高。
就當(dāng)前而言盅蝗,基座模式實(shí)施起來(lái)比較方便鳖链,方案上便也是蠻多的。
而不論種方式墩莫,都需要提供一個(gè)查找應(yīng)用的機(jī)制芙委,在微前端中稱(chēng)為服務(wù)的注冊(cè)表模式。和微服務(wù)架構(gòu)相似狂秦,不論是哪種微前端方式灌侣,也都需要有一個(gè)應(yīng)用注冊(cè)表的服務(wù),它可以是一個(gè)固定值的配置文件裂问,如 JSON 文件侧啼,又或者是一個(gè)可動(dòng)態(tài)更新的配置,又或者是一種動(dòng)態(tài)的服務(wù)堪簿。它主要做這么一些內(nèi)容:
- 應(yīng)用發(fā)現(xiàn)痊乾。讓主應(yīng)用可以尋找到其它應(yīng)用。
- 應(yīng)用注冊(cè)椭更。即提供新的微前端應(yīng)用哪审,向應(yīng)用注冊(cè)表注冊(cè)的功能。
- 第三方應(yīng)用注冊(cè)甜孤。即讓第三方應(yīng)用协饲,可以接入到系統(tǒng)中。
- 訪(fǎng)問(wèn)權(quán)限等相關(guān)配置缴川。
應(yīng)用在部署的時(shí)候茉稠,便可以在注冊(cè)表服務(wù)中注冊(cè)。如果是基于注冊(cè)表來(lái)管理應(yīng)用把夸,那么使用基座模式來(lái)開(kāi)發(fā)比較方便而线。
設(shè)計(jì)理念
在筆者實(shí)踐微前端的過(guò)程中,發(fā)現(xiàn)了以下幾點(diǎn)是我們?cè)谠O(shè)計(jì)的過(guò)程中恋日,需要關(guān)注的內(nèi)容:
- 中心化:應(yīng)用注冊(cè)表膀篮。這個(gè)應(yīng)用注冊(cè)表?yè)碛忻總€(gè)應(yīng)用及對(duì)應(yīng)的入口。在前端領(lǐng)域里岂膳,入口的直接表現(xiàn)形式可以是路由誓竿,又或者對(duì)應(yīng)的應(yīng)用映射。
- 標(biāo)識(shí)化應(yīng)用谈截。 我們需要一個(gè)標(biāo)識(shí)符來(lái)標(biāo)識(shí)不同的應(yīng)用筷屡,以便于在安裝涧偷、卸載的時(shí)候,能尋找到指定的應(yīng)用毙死。一個(gè)簡(jiǎn)單的模式燎潮,就是通過(guò)康威定律來(lái)命名應(yīng)用。
- 應(yīng)用生命周期管理扼倘。
- 高內(nèi)聚确封,低耦合。
生命周期
前端微架構(gòu)與后端微架構(gòu)的最大不同之處再菊,也在于此——生命周期爪喘。微前端應(yīng)用作為一個(gè)客戶(hù)端應(yīng)用,每個(gè)應(yīng)用都擁有自己的生命周期:
- Load袄简,決定加載哪個(gè)應(yīng)用腥放,并綁定生命周期
- bootstrap,獲取靜態(tài)資源
- Mount绿语,安裝應(yīng)用秃症,如創(chuàng)建 DOM 節(jié)點(diǎn)
- Unload,刪除應(yīng)用的生命周期
- Unmount吕粹,卸載應(yīng)用种柑,如刪除 DOM 節(jié)點(diǎn)、取消事件綁定
這部分的內(nèi)容事實(shí)上匹耕,也就是微前端的一個(gè)難點(diǎn)所在聚请,如何以合適的方式來(lái)加載應(yīng)用——畢竟每個(gè)前端框架都各自不同,其所需要的加載方式也是不同的稳其。當(dāng)我們決定支持多個(gè)框架的時(shí)候驶赏,便需要在這一部分進(jìn)入更細(xì)致的研究。
如何拆分既鞠?
隨后煤傍,我們要面臨的一個(gè)挑戰(zhàn)是:如何去拆分應(yīng)用。
技術(shù)方式
從技術(shù)實(shí)踐上嘱蛋,微前端架構(gòu)可以采用以下的幾種方式進(jìn)行:
- 路由分發(fā)式蚯姆。通過(guò) HTTP 服務(wù)器的反向代理功能,來(lái)將請(qǐng)求路由到對(duì)應(yīng)的應(yīng)用上洒敏。
- 前端微服務(wù)化龄恋。在不同的框架之上設(shè)計(jì)通訊、加載機(jī)制凶伙,以在一個(gè)頁(yè)面內(nèi)加載對(duì)應(yīng)的應(yīng)用郭毕。
- 微應(yīng)用。通過(guò)軟件工程的方式函荣,在部署構(gòu)建環(huán)境中显押,組合多個(gè)獨(dú)立應(yīng)用成一個(gè)單體應(yīng)用链韭。
- 微件化。開(kāi)發(fā)一個(gè)新的構(gòu)建系統(tǒng)煮落,將部分業(yè)務(wù)功能構(gòu)建成一個(gè)獨(dú)立的 chunk 代碼,使用時(shí)只需要遠(yuǎn)程加載即可踊谋。
- 前端容器化蝉仇。通過(guò)將 iFrame 作為容器,來(lái)容納其它前端應(yīng)用殖蚕。
- 應(yīng)用組件化轿衔。借助于 Web Components 技術(shù),來(lái)構(gòu)建跨框架的前端應(yīng)用睦疫。
實(shí)施的方式雖然多害驹,但是都是依據(jù)場(chǎng)景而采用的。有些場(chǎng)景下蛤育,可能沒(méi)有合適的方式宛官;有些場(chǎng)景下,則可以同時(shí)使用多種方案瓦糕。
路由分發(fā)式
路由分發(fā)式微前端底洗,即通過(guò)路由將不同的業(yè)務(wù)分發(fā)到不同的、獨(dú)立前端應(yīng)用上咕娄。其通澈ヒ荆可以通過(guò) HTTP 服務(wù)器的反向代理來(lái)實(shí)現(xiàn)唉擂,又或者是應(yīng)用框架自帶的路由來(lái)解決铃剔。如下圖所示:
就當(dāng)前而言,路由分發(fā)式的架構(gòu)應(yīng)該是采用最多漆撞、最容易的 “微前端” 方案圣贸。但是
前端微服務(wù)化
前端微服務(wù)化挚歧,是微服務(wù)架構(gòu)在前端的實(shí)施,每個(gè)前端應(yīng)用都是完全獨(dú)立(技術(shù)棧旁趟、開(kāi)發(fā)昼激、部署、構(gòu)建獨(dú)立)锡搜、自主運(yùn)行的橙困,最后通過(guò)模塊化的方式組合出完整的前端應(yīng)用。其架構(gòu)如下圖所示:
采用這種方式意味著耕餐,一個(gè)頁(yè)面上同時(shí)存在二個(gè)及以上的前端應(yīng)用在運(yùn)行凡傅。而路由分發(fā)式方案,則是一個(gè)頁(yè)面只有唯一一個(gè)應(yīng)用肠缔。
組合式集成:微應(yīng)用化
微應(yīng)用化夏跷,即在開(kāi)發(fā)時(shí)哼转,應(yīng)用都是以單一、微小應(yīng)用的形式存在槽华,而在運(yùn)行時(shí)壹蔓,則通過(guò)構(gòu)建系統(tǒng)合并這些應(yīng)用,組合成一個(gè)新的應(yīng)用猫态。其架構(gòu)如下圖所示:
微應(yīng)用化更多的是以軟件工程的方式佣蓉,來(lái)完成前端應(yīng)用的開(kāi)發(fā),因此又可以稱(chēng)之為組合式集成亲雪。對(duì)于一個(gè)大型的前端應(yīng)用來(lái)說(shuō)勇凭,采用的架構(gòu)方式,往往會(huì)是通過(guò)業(yè)務(wù)作為主目錄义辕,而后在業(yè)務(wù)目錄中放置相關(guān)的組件虾标,同時(shí)擁有一些通用的共享模板。
微件化
微件(widget)灌砖,指的是一段可以直接嵌入在應(yīng)用上運(yùn)行的代碼璧函,它由開(kāi)發(fā)人員預(yù)先編譯好,在加載時(shí)不需要再做任何修改或者編譯周崭。而微前端下的微件化則指的是柳譬,每個(gè)業(yè)務(wù)團(tuán)隊(duì)編寫(xiě)自己的業(yè)務(wù)代碼,并將編譯好的代碼部署(上傳或者放置)到指定的服務(wù)器上续镇,在運(yùn)行時(shí)美澳,我們只需要加載相應(yīng)的業(yè)務(wù)模塊即可。對(duì)應(yīng)的摸航,在更新代碼的時(shí)候制跟,我們只需要更新對(duì)應(yīng)的模塊即可。下圖便是微件化的架構(gòu)示意圖:
在非單面應(yīng)用時(shí)代酱虎,要實(shí)現(xiàn)微件化方案雨膨,是一件特別容易的事。從遠(yuǎn)程加載來(lái)對(duì)應(yīng)的 JavaScript 代碼读串,在瀏覽器上執(zhí)行聊记,生成對(duì)應(yīng)的組件嵌入到頁(yè)面的相應(yīng)部分。對(duì)于業(yè)務(wù)組件也是類(lèi)似的恢暖,提前編寫(xiě)好我們的業(yè)務(wù)組件排监,當(dāng)需要對(duì)應(yīng)的組件時(shí)再響應(yīng)、執(zhí)行杰捂。
前端容器化
前端容器:iframe
iframe 作為一個(gè)非常 “古老” 的舆床,人人都覺(jué)得普通的技術(shù),卻一直很管用。它能有效地將另一個(gè)網(wǎng)頁(yè)/單頁(yè)面應(yīng)用嵌入到當(dāng)前頁(yè)面中挨队,兩個(gè)頁(yè)面間的 CSS 和 JavaScript 是相互隔離的——除去 iframe 父子通信部分的代碼谷暮,它們之間的代碼是完全不相互干擾的。iframe 便相當(dāng)于是創(chuàng)建了一個(gè)全新的獨(dú)立的宿主環(huán)境盛垦,類(lèi)似于沙箱隔離湿弦,它意味著前端應(yīng)用之間可以相互獨(dú)立運(yùn)行。
結(jié)合 Web Components 構(gòu)建
Web Components 是一套不同的技術(shù)腾夯,允許開(kāi)發(fā)者創(chuàng)建可重用的定制元素(它們的功能封裝在代碼之外)省撑,并且在 web 應(yīng)用中使用它們。
目前困擾 Web Components 技術(shù)推廣的主要因素俯在,在于瀏覽器的支持程度。在 Chrome 和 Opera 瀏覽器上娃惯,對(duì)于 Web Components 支持良好跷乐,而對(duì)于 Safari、IE趾浅、Firefox 瀏覽器的支持程度愕提,并沒(méi)有那么理想。
業(yè)務(wù)拆分
與微服務(wù)類(lèi)似皿哨,要?jiǎng)澐植煌那岸诉吔缜城龋皇且患菀椎氖隆>彤?dāng)前而言证膨,以下幾種方式是常見(jiàn)的劃分微前端的方式:
- 按照業(yè)務(wù)拆分如输。
- 按照權(quán)限拆分。
- 按照變更的頻率拆分央勒。
- 按照組織結(jié)構(gòu)拆分不见。利用康威定律來(lái)進(jìn)一步拆分前端應(yīng)用。
- 跟隨后端微服務(wù)劃分崔步。實(shí)踐證明稳吮, DDD 與事件風(fēng)暴是一種頗為有效的后端微前端拆分模式,對(duì)于前端來(lái)說(shuō)井濒,它也頗有有效——直接跟蹤后端服務(wù)灶似。
每個(gè)項(xiàng)目都有自己特殊的背景,切分微前端的方式便不一樣瑞你。即使項(xiàng)目的類(lèi)型相似酪惭,也存在一些細(xì)微的差異。
微前端之外
如果微前端對(duì)于你們來(lái)說(shuō)困境重重捏悬,還有一些不錯(cuò)的架構(gòu)模式可以采用撞蚕。
應(yīng)用微化架構(gòu)
應(yīng)用微化架構(gòu),是一種開(kāi)發(fā)時(shí)整體过牙,構(gòu)建時(shí)拆分甥厦,運(yùn)行時(shí)分離的前端架構(gòu)模式纺铭。即應(yīng)用微化架構(gòu)從一份代碼中,構(gòu)建出適用于不同環(huán)境的多套目標(biāo)代碼刀疙。實(shí)現(xiàn)上它是一種:
- 構(gòu)建時(shí)拆分架構(gòu)舶赔。
- 代碼刪除架構(gòu)。笑谦秧,以刪代碼的方式竟纳,來(lái)形成每個(gè)前端應(yīng)用。
- 微前端準(zhǔn)備式架構(gòu)疚鲤。即锥累,隨時(shí)可以拆分為多個(gè)前端應(yīng)用。
由于它與微應(yīng)用化的相似性集歇,我們將它與微應(yīng)用化做一個(gè)對(duì)比桶略。它與微應(yīng)用化不同的是,應(yīng)用微化是在構(gòu)建時(shí)對(duì)應(yīng)用進(jìn)行拆分诲宇,而非在本地模式下對(duì)應(yīng)用拆分际歼。相似的是,它也是基于構(gòu)建系統(tǒng)的應(yīng)用拆分方式姑蓝。
即:微應(yīng)用化鹅心,是一個(gè)隨時(shí)可合并式架構(gòu)。而應(yīng)用微化纺荧,則是一個(gè)隨時(shí)可拆分式架構(gòu)旭愧。
它不僅僅是一個(gè)適合于前端的架構(gòu)模式,也是一適用于后端的架構(gòu)模式宙暇。
整潔前端架構(gòu)
Clean Architecture 是由 Robert C. Martin 在 2012 年提出的架構(gòu)模式榕茧。它具有這么一些特點(diǎn):框架無(wú)關(guān)性、可被測(cè)試客给、UI 無(wú)關(guān)性用押、數(shù)據(jù)庫(kù)無(wú)關(guān)性、外部機(jī)構(gòu)(agency)無(wú)關(guān)性靶剑。
對(duì)于前端架構(gòu)來(lái)說(shuō)蜻拨,Clean Architecure 實(shí)際上是:Clean Architecture + MVP + 組件化。如下圖所示:
考慮到應(yīng)用的規(guī)模桩引,這里以 Angular + TypeScript 作為示例:
這種架構(gòu)模式特別適合于:組織內(nèi)即寫(xiě)前端又同后端的團(tuán)隊(duì)缎讼。它易于映射前后端 API,且可以使用 usecase 作為防腐層坑匠。
沒(méi)有銀彈血崭。不得不提及的是,對(duì)于小規(guī)模的團(tuán)隊(duì)來(lái)說(shuō),它帶來(lái)的弊端可能會(huì)遠(yuǎn)遠(yuǎn)大于好處——帶來(lái)大量冗余的代碼夹纫。盡管通過(guò) Angular Schematics 可以通過(guò)參數(shù)來(lái)生成代碼咽瓷,但是這種分層架構(gòu)地于簡(jiǎn)單的應(yīng)用來(lái)說(shuō),還是過(guò)于復(fù)雜舰讹、難以上手茅姜。對(duì)于不寫(xiě)測(cè)試的項(xiàng)目來(lái)說(shuō) ,usecase 也不能帶來(lái)它們所承諾的好處月匣。
結(jié)論
微前端不是銀彈钻洒,當(dāng)前也沒(méi)有最佳實(shí)踐,但是這是一個(gè)非常好的學(xué)習(xí)機(jī)會(huì)锄开。
文/ThoughtWorks黃峰達(dá)
更多精彩洞見(jiàn)素标,請(qǐng)關(guān)注微信公眾號(hào):ThoughtWorks洞見(jiàn)