type
status
date
slug
summary
tags
category
icon
password
微服務 (Microservices) 是近年來隨著容器化技術成熟而變得非常流行的一種軟體系統架構。雖然本篇文章的主角並不是它,不過它背後的理念與實踐方式與今天的主角「Micro frontends 微前端」是高度相關的,因此我們還是有必要來好好理解一下它的基本概念。
要了解微服務,最簡單的方式就是與過往熟悉的開發架構,也就是單體式 (Monolithic) 的架構來做個比較:
單體式架構舉例來說就是假設你使用任意一個 backend framework 建立了一個 RESTful API service,其中可能會有 Authentication、購物車、發文等不同的商業邏輯,在單體式架構下會將這些不同的商業邏輯(或稱服務)寫在同一個應用中,並且這些服務通常會對同一個資料來源(例如資料庫)做存取與寫入,部署也需要一同部署。這樣的架構其實缺點很明顯,就是無法達到高可擴展性與高效率的組織開發,而 Microservices 的出現則充分解決了單體架構的缺陷。
Microservices 微服務架構將原本複雜的應用,依照商業邏輯切分成一個個服務,每個服務也會有各自的資料庫與伺服器,例如上面舉的 Authentication、購物車、發文就可以拆分成三個服務,這樣的好處是不同的服務可以獨立擴展與部署,而不會被其他服務制約,每個服務也可以使用不同的技術去建構,例如購物車用 Node.js 寫,發文功能則使用 Go 語言建構。而容器化技術的成熟也讓各個服務的部署與擴展更加方便,因此 Microservices 也漸漸成為主流的架構・在這個架構下比較複雜的問題就是不同服務之間的溝通了,HTTP RESTful API、Event Bus、Message Queue 是常見的一些選擇,不過這就在本篇文章的範圍之外了。
看起來 Microservices 是後端的架構,那前端呢?
沒錯,上面介紹的 Microservices 的確是後端的架構,前端生態大多仍然維持單體式的架構,不過隨著近幾年 web app 的複雜化,許多前面提到的單體式架構的缺點又漸漸浮現出來。當越來越多人注意到這個問題後,有人就開始思考「Microservices 的架構是否也可以運用在前端領域?」也因此 Micro Frontends 這個架構想法才正式出現。
What’s Micro Frontends ?
這也是我們前面為什麼要講 Microservices 的原因,因為基本上概念是一樣的,就是將龐大的前端專案依照商業邏輯拆分成不同的 module,每個 module 可以由不同的技術團隊維護,也可以使用不同技術或框架建構(例如購物車模組使用 React,評論模組則使用 Vue),甚至各個模組也可以獨立擴展與部署,達成「在團隊或模組間低耦合,團隊或模組中高內聚。」的特性。如果把上面單體式前端架構搭配 Microservices 後端的架構圖改成使用 Micro Frontends 的話,會是這樣子:
前面提過 Microservices 的技術難點在於不同服務之間的溝通,而 Micro frontends 的技術難點也大同小異,也就是不同 module 的組合與溝通,畢竟使用者看到的頁面只會有一個,背後不同模組怎麼組裝與溝通則是我們工程師要去煩惱的事。Micro Frontends 要考慮的點跟 Microservices 有點不太一樣,例如路由與狀態的共享…等,不過這些比較進階的議題也不在本篇文章的範圍中,有興趣的讀者麻煩自行研究囉!
如何做到 Micro Frontends ?
就像前面提到 Microservices 的服務間的溝通方式有很多種一樣,要做到 Micro Frontends 其實也有非常多方法,其中較常見的有以下幾種:
- 使用 iframe 拼裝
- Client Side 利用 JavaScript 載入模組
- Web Component
- Webpack Module Federation
如果上網查詢 Micro Frontends 的實作方法的話,Web Component 應該會是最多資源的一種方式,不過因為現今前端開發幾乎都仰賴如 React 或 Vue 這種 framework 或 library 來開發,雖然 web component 可以與各種框架一起使用,但這種方式畢竟還是造成開發體驗不好,也違背了各個框架的設計原則。也因此 Micro Frontends 這個新的架構並沒有出現即造成轟動,反倒是沉寂了一段時間,直到 webpack 5 推出了 Module Federation,才讓 Micro Frontends 架構又再次被拿出來熱烈討論與嘗試。
Webpack Module Federation
以往使用 webpack 的最主要的目的就是需要將專案打包,不過當應用程式或展變得越來越複雜之後,打包後的檔案會變得非常肥大,影響瀏覽器載入網頁的時間,如果有看過我之前關於效能優化的這篇文章:
會知道 dynamic import 或是 code splitting 這些把肥大檔案切分成多個 chunk 來提升載入速度的做法,不過問題來了,這些方法有個限制
只能引入同個專案來源的資源
而 Module Federation 的出現則解開了這個限制,它讓我們可以引入「其他專案」 bundle 過後的程式,為 Micro Frontends 的發展開啟了一道曙光。
Demo Time
接下來的 Demo 將用純 HTML/CSS/JS 三劍客來示範,不會使用任何前端框架,目的是為了讓讀者了解 Module Federation 是怎麼運作的,如果未來有機會會再寫一篇文章介紹不同前端框架的組合,如果迫切想知道的讀者再麻煩自行研究了🙏
接下來不會一步一步講解過程,而是挑重點的部分說明,最後會放上完整的 demo 程式碼,讀者們不用擔心。
今天要 demo 的應用很簡單,總共有三個 component (這邊的 component 指的是 Micro Frontends 的一個個 module),分別為 Cart、Posts 與裝它們的容器 Container,這個 demo 也不會真的做出購物車跟發文,只會以文字替代,因為這點在於說明不同裝案間如何整合來達到 Micro Frontends 的架構。
基本上三個 component 的專案結構目前都長這樣子
每個 component 專案中都會有一個 webpack.config.js 來設定 Module Federation 的 config,也就是這次 demo 的核心所在。public 資料夾中放的是一個 index.html 檔案,不過有人也許會疑惑按照道理只有 container 需要有 HTML 檔案吧?畢竟它的功能就是提供 DOM 元素給其他 component 注入。說得沒錯!不過,不要忘記 Micro Frontends 的一個優勢:「獨立開發,獨立測試」,因此每個 component 也可以在 local development 環境下各自渲染到自定義的 HTML 中進行測試,這是為什麼每個 component 中仍然放了一個 index.html 的原因。src 裡面的 js 檔則是做把簡單文字插入 DOM 元素裡,因為很簡單,建議讀者直接找下面的 github demo repo 來看。
再來看看 package.json
要特別注意 webpack 的版本需要在 5 以上才有 Module Federation 的功能喔!
接下來看重點檔案,也就是 webpack.config.js,我們需要了解這個 demo 只有兩種角色:source(來源,例如 cart)與 target (目標,例如 container),而 webpack.config.js 也只需要針對這兩種角色做不同設定而已,讓我們先來看看作為 source 的 Cart component 的 config:
再來看看作為 target 的 Container component 的 config:
需要看的其實只有 ModuleFeferationPlugin 那個 block,cart 因為是 source,因此需要把自己 export 出去,需要給它一個名字(name: cart),fileName 則是在 container 做引入時會用到,這邊使用官方建議使用的 remoteEntry.js 作為檔案名。exposes 那邊的意思是 「未來有別的地方要引入 CartShow,就自動去 ./src/cart 幫我找。」其實就是做 path alias,避免檔案層級過多造成引入容易發生錯誤與程式碼不易讀。shared 則是當不同 component 間有共同依賴的第三方套件時要去設定的,這個 demo 就不多做說明。另外 posts 這個 component 的 webpack config 因為跟 cart 一樣都是 source,所以大同小異,這邊就不另外附上程式碼圖片囉!
將 source export 出去後,再來就是在 target 也就是 container 將 source import 進來。語法非常簡單,在 remotes 這個 object 中寫下要引入的 source,以 cart 為例子
這邊的 key 名字要記好,等等引入時會用到。@左邊的 cart 必須對應剛剛在 cart 的 webpack.config.js 中定義的 name,8082 也是在 webpack config 中定義的 devServer 的 port,remoteEntry.js 則是剛剛定義的 fileName,如此一來我們就把 cart 這個從外部專案來的 component (或稱 module) 給引入到 container 裡面囉!最後就來看看在 container 專案中要怎麼使用引入進來的 component
前面沒有講解 cart 跟 products 到底寫了些什麼 code,不過其實就是各別 export 出一個 mount 的 function 來處理 DOM 的渲染。
cart/CartShow 前面的 cart 就是我在不久前說過在 webpack import remotes 時要記好的 key (必須一致),CartShow 則是在 cart 的 webpack config 說過做 path alias 的 key:
也就是說 webpack 看到 cart/CartShow 就會自動到 cart 專案底下的 ./src/cart 去抓被 export 的程式碼出來用。
最後把三個專案的 dev server 都開起來(畢竟要還是得靠 http 來溝通與組合),再打開瀏覽器輸入 container 的 url : http://localhost:8080
就可以看到以下畫面:
我們的超簡易 Micro Frontends Demo 就完成了!
附上 github repo
Micro Frontends 的優缺點與適用場景
Micro Frontends 擁有跟 Microservices 相似的優點:
- 模組可以獨立部署、獨立擴展
- 團隊組織可以明確拆分
- 更容易程式碼的 refactor 與維護
而缺點同樣也很明顯:
- 複雜度
- 效能
不過在文中提到的 module federation 出現以後,複雜度會小一點(以前用 iframe 可能就要透過 post message,或是 web component 跟框架混用的狀況,這些方式效能也會比較差)
適用情境我認為要看業務需求,小專案用了可能成本還大於效益,目前我認為大型專案或是跨國專案比較適合採用這樣的架構。
這個網站有列一些有使用微前端架構的公司,提供給讀者參考
結語
其實有太多東西沒有介紹到了,例如不同 module 間的套件要如何共享?Navigation 要怎麼做?部署又該怎麼進行?甚至更複雜的跨 module 的 Authentication 跟效能優化,都是值得且必須去深入研究的。Micro Frontends 仍然是一個不夠成熟的架構,不過我想在了解過它的概念後大家都能看出它的潛在發展性,因此我認為持續關注它是不會吃虧的,也許未來某一天它也會擁有像 Microservices 在後端世界裡相同的地位,Let’s wait and see ! 最後希望這篇文章能讓讀者除了了解 Micro Frontends 是什麼以外,也同樣激起對它的興趣與期待。
- 作者:墨綠B.G.
- 連結:https://www.blackishgreen.link//article/micro-frontends
- 著作權:本文採用 CC BY-NC-SA 4.0 許可協議,轉載請註明出處。