實施微前端的六種方式(上):三種藉助路由微服務化前端應用 原

微前端架構是一種類似於微服務的架構,它將微服務的理念應用於瀏覽器端,即將 Web 應用由單一的單體應用轉變爲多個小型前端應用聚合爲一的應用

由此帶來的變化是,這些前端應用可以獨立運行獨立開發獨立部署。以及,它們應該可以在共享組件的同時進行並行開發——這些組件可以通過 NPM 或者 Git Tag、Git Submodule 來管理。

注意:這裏的前端應用指的是前後端分離的單應用頁面,在這基礎才談論微前端纔有意義。

結合我最近半年在微前端方面的實踐和研究來看,微前端架構一般可以由以下幾種方式進行:

  1. 使用 HTTP 服務器的路由來重定向多個應用
  2. 在不同的框架之上設計通訊、加載機制,諸如 MooaSingle-SPA
  3. 通過組合多個獨立應用、組件來構建一個單體應用
  4. 使用純 WebComponent 構建應用
  5. iFrame。使用 iFrame 及自定義消息傳遞機制
  6. 結合 WebComponent 和 ShadowDOM 來隔離應用

不同的方式適用於不同的使用場景,當然也可以組合一起使用。那麼,就讓我們來一一瞭解一下,爲以後的架構演進做一些技術鋪墊。

基礎鋪墊:應用分發路由 -> 路由分發應用

在一個單體前端、單體後端應用中,有一個典型的特徵,即路由是由框架來分發的,框架將路由指定到對應的組件或者內部服務中。微服務在這個過程中做的事情是,將調用由函數調用變成了遠程調用,諸如遠程 HTTP 調用。而微前端呢,也是類似的,它是將應用內的組件調用變成了更細粒度的應用間組件調用,即原先我們只是將路由分發到應用的組件執行,現在則需要根據路由來找到對應的應用,再由應用分發到對應的組件上。

後端:函數調用 -> 遠程調用

在大多數的 CRUD 類型的 Web 應用中,也都存在一些極爲相似的模式,即:首頁 -> 列表 -> 詳情:

  • 首頁,用於面向用戶展示特定的數據或頁面。這些數據通常是有限個數的,並且是多種模型的。
  • 列表,即數據模型的聚合,其典型特點是某一類數據的集合,可以看到儘可能多的數據概要(如 Google 只返回 100 頁),典型見 Google、淘寶、京東的搜索結果頁。
  • 詳情,展示一個數據的儘可能多的內容。

如下是一個 Spring 框架,用於返回首頁的示例:

@RequestMapping(value="/")
public ModelAndView homePage(){
   return new ModelAndView("/WEB-INF/jsp/index.jsp");
}

對於某個詳情頁面來說,它可能是這樣的:

@RequestMapping(value="/detail/{detailId}")
public ModelAndView detail(HttpServletRequest request, ModelMap model){
   ....
   return new ModelAndView("/WEB-INF/jsp/detail.jsp", "detail", detail);
}

那麼,在微服務的情況下,它則會變成這樣子:

@RequestMapping("/name")
public String name(){
    String name = restTemplate.getForObject("http://account/name", String.class);
    return Name" + name;
}

前端:組件調用 -> 應用調用

在形式上來說,單體前端框架的路由和單體後端應用,並沒有太大的區別:依據不同的路由,來返回不同頁面的模板。

const appRoutes: Routes = [
  { path: 'index', component: IndexComponent },
  { path: 'detail/:id', component: DetailComponent },
];

而當我們將之微服務化後,則可能變成應用 A 的路由:

const appRoutes: Routes = [
  { path: 'index', component: IndexComponent },
];

外加之應用 B 的路由:

const appRoutes: Routes = [
  { path: 'detail/:id', component: DetailComponent },
];

而問題的關鍵就在於:怎麼將路由分發到這些不同的應用中去

路由分發式微前端

路由分發式微前端,即通過路由將不同的業務分發到不同的、獨立前端應用上。其通常可以通過 HTTP 服務器的反向代理來實現,又或者是應用框架自帶的路由來解決。

就當前而言,通過路由分發式的微前端架構應該是採用最多、最易採用的 “微前端” 方案。但是這種方式看上去更像是多個前端應用的聚合,即我們只是將這些不同的前端應用拼湊到一起,使他們看起來像是一個完整的整體。但是它們並不是,每次用戶從 A 應用到 B 應用的時候,往往需要刷新一下頁面。

在幾年前的一個項目裏,我們當時正在進行遺留系統重寫。我們制定了一個遷移計劃:

  1. 首先,使用靜態網站生成動態生成首頁
  2. 其次,使用 React 計劃棧重構詳情頁
  3. 最後,替換搜索結果頁

整個系統並不是一次性遷移過去,而是一步步往下進行。因此在完成不同的步驟時,我們就需要上線這個功能,於是就需要使用 Nginx 來進行路由分發。

如下是一個基於路由分發的 Nginx 配置示例:

http {
  server {
    listen       80;
    server_name  www.phodal.com;
    location /api/ {
      proxy_pass http://http://172.31.25.15:8000/api;
    }
    location /web/admin {
      proxy_pass http://172.31.25.29/web/admin;
    }
    location /web/notifications {
      proxy_pass http://172.31.25.27/web/notifications;
    }
    location / {
      proxy_pass /;
    }
  }
}

在這個示例裏,不同的頁面的請求被分發到不同的服務器上。

隨後,我們在別的項目上也使用了類似的方式,其主要原因是:跨團隊的協作。當團隊達到一定規模的時候,我們不得不面對這個問題。除此,還有 Angluar 跳崖式升級的問題。於是,在這種情況下,用戶前臺使用 Angular 重寫,後臺繼續使用 Angular.js 等保持再有的技術棧。在不同的場景下,都有一些相似的技術決策。

因此在這種情況下,它適用於以下場景:

  • 不同技術棧之間差異比較大,難以兼容、遷移、改造
  • 項目不想花費大量的時間在這個系統的改造上
  • 現有的系統在未來將會被取代
  • 系統功能已經很完善,基本不會有新需求

而在滿足上面場景的情況下,如果爲了更好的用戶體驗,還可以採用 iframe 的方式來解決。

使用 iFrame 創建容器

iFrame 作爲一個非常古老的,人人都覺得普通的技術,卻一直很管用。

HTML 內聯框架元素 <iframe> 表示嵌套的正在瀏覽的上下文,能有效地將另一個 HTML 頁面嵌入到當前頁面中。

iframe 可以創建一個全新的獨立的宿主環境,這意味着我們的前端應用之間可以相互獨立運行。採用 iframe 有幾個重要的前提:

  • 網站不需要 SEO 支持
  • 擁有相應的應用管理機制

如果我們做的是一個應用平臺,會在我們的系統中集成第三方系統,或者多個不同部門團隊下的系統,顯然這是一個不錯的方案。一些典型的場景,如傳統的 Desktop 應用遷移到 Web 應用:

Angular Tabs 示例

如果這一類應用過於複雜,那麼它必然是要進行微服務化的拆分。因此,在採用 iframe 的時候,我們需要做這麼兩件事:

  • 設計管理應用機制
  • 設計應用通訊機制

加載機制。在什麼情況下,我們會去加載、卸載這些應用;在這個過程中,採用怎樣的動畫過渡,讓用戶看起來更加自然。

通訊機制。直接在每個應用中創建 postMessage 事件並監聽,並不是一個友好的事情。其本身對於應用的侵入性太強,因此通過 iframeEl.contentWindow 去獲取 iFrame 元素的 Window 對象是一個更簡化的做法。隨後,就需要定義一套通訊規範:事件名採用什麼格式、什麼時候開始監聽事件等等。

有興趣的讀者,可以看看筆者之前寫的微前端框架:Mooa

不管怎樣,iframe 對於我們今年的 KPI 怕是帶不來一絲的好處,那麼我們就去造個輪子吧。

自制框架兼容應用

不論是基於 WebComponent 的 Angular,或者是 VirtualDOM 的 React 等,現有的前端框架都離不開基本的 HTML 元素 DOM。

那麼,我們只需要:

  1. 在頁面合適的地方引入或者創建 DOM
  2. 用戶操作時,加載對應的應用(觸發應用的啓動),並能卸載應用。

第一個問題,創建 DOM 是一個容易解決的問題。而第二個問題,則一點兒不容易,特別是移除 DOM 和相應應用的監聽。當我們擁有一個不同的技術棧時,我們就需要有針對性設計出一套這樣的邏輯。

儘管 Single-SPA 已經擁有了大部分框架(如 React、Angular、Vue 等框架)的啓動和卸載處理,但是它仍然不是適合於生產用途。當我基於 Single-SPA 爲 Angular 框架設計一個微前端架構的應用時,我最後選擇重寫一個自己的框架,即 Mooa

雖然,這種方式的上手難度相對比較高,但是後期訂製及可維護性比較方便。在不考慮每次加載應用帶來的用戶體驗問題,其唯一存在的風險可能是:第三方庫不兼容

但是,不論怎樣,與 iFrame 相比,其在技術上更具有可吹牛逼性,更有看點。同樣的,與 iframe 類似,我們仍然面對着一系列的不大不小的問題:

  • 需要設計一套管理應用的機制。
  • 對於流量大的 toC 應用來說,會在首次加載的時候,會多出大量的請求

而我們即又要拆分應用,又想 blabla……,我們還能怎麼做?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章