微核心架構在大型前端系統中的應用

YataoZhang發表於2019-02-18

微核心架構在大型前端系統中的應用

只討論架構,不討論框架

1、名詞解釋

由一群儘可能將數量最小化的軟體程式組成,他們負責提供、實現一個作業系統所需要的各種機制和功能。這些最基礎的機制,包括了底層地址空間管理,執行緒管理,與程式間通訊。

2、設計理念

將系統的實現,與系統的基本操作規則區分開來。它實現的方式是將核心功能模組化,劃分成幾個獨立的程式,各自執行,這些程式被稱為服務。所有的服務程式,都執行在不同的地址空間。

讓服務各自獨立,可以減少系統之間的耦合度,易於實現與除錯,也可以增進可移植性。它可以避免單一元件失效,而造成整個系統崩潰,核心只需要重啟這個元件,不至於影響其他伺服器的功能,使系統穩定度增加。同時業務功能可以視需要,抽換或新增某些服務程式,使功能更有彈性

就程式碼數量來看,一般來說,因為功能簡化,核心系統使用的程式碼比整合式系統更少。更少的程式碼意味更少的潛藏程式bug

3、具體應用

微核心架構在使用時主要考慮兩個方面『核心系統』和『外掛模組』。應用邏輯被劃分為獨立的『核心系統』和『外掛模組』,這樣就提供了良好的可擴充套件性靈活性,應用的新特性和基礎業務邏輯也會被隔離

一、核心系統

核心系統通常是一個可以獨立執行的最小化模組,作業系統(Windows NT、Mac OS X)就是這麼實現的。從商業應用的角度來看,核心系統為那些特定的場景、規則、複雜的條件判斷提供了通用的業務邏輯,而外掛模組則提供了更為具體的業務邏輯。可以增加或擴充套件核心系統以達到產生附加的業務邏輯的能力。

二、外掛模組

外掛模組通常是一個專業處理額外特性的獨立元件。通常,外掛模組之間是沒有依賴的,當然你也可以建立一個依賴其他外掛模組的外掛,但不管怎麼樣,讓外掛模組之間可以彼此通訊又不產生依賴是一個很重要的問題。

三、獲取外掛模組並判斷可用性

核心系統需要知道每個外掛的可用性並且知道如何獲取它們,一個通常的實現方式是使用一組登錄檔。登錄檔包括了每個外掛的基本資訊,包括名稱資料規範遠端訪問協議(取決於外掛模組如何和核心繫統進行連線)以及其他自定義資料。比如百度網盤中用於上傳檔案的上傳外掛提供了外掛名稱、資料規範(輸入、輸出資料)、資料格式(json、xml),如果這個外掛是通過非同步進行載入的,那麼還會有一個具體遠端HTTP訪問協議地址。

四、連線到核心系統

外掛模組可以通過多種方式連線到核心系統,包括OSGI(open service gateway initiative)訊息機制web服務以及點對點的繫結(物件例項化,既依賴注入)。使用何種方式主要取決於具體的應用場景和特殊需求(單機部署、分散式部署),微核心架構預設沒有要求具體的實現方式,但是必須保證外掛模組之間不能產生任何依賴。

五、通訊規範

外掛模組和核心繫統之間的通訊規範分為標準規範自定義規範,自定義規範通常是指某個外掛模組是由第三方服務開發的。這種情況下,就需要在自定義規範和標準規範之間提供一個Adapter,這樣核心系統就不需要關心每個外掛模組的具體實現。在設計標準規範之前制定一個版本策略很重要。

六、事件模式

核心系統提供了多種事件模式,主要包括常用的點對點模式、釋出訂閱模式。同時,事件的型別分為全域性(系統級)事件、系統內部事件以及外掛模組內部事件。由於點對點模式中傳送者和接收者之間沒有依賴關係並且一條訊息只對應一個接收者,所以可以用作廣播全域性(系統級)事件,比如調起某個外掛模組。而釋出訂閱模式中訂閱者和釋出者之間存在時間上的依賴性,可以用於系統內部事件和外掛模組的內部事件。此外,核心模組也可以通過釋出訂閱模式向外釋出某些屬於業務基本操作規則的事件。

七、介面設計

當外掛模組註冊到核心系統之後,通過系統級事件可以調起具體的某外掛模組。此時就需要核心模組提供屬於基本操作規則的介面供外掛模組使用,同樣的,外掛模組也必須按照通訊規範提供執行入口(類似於java的Main方法)資料規範(引數格式,返回的資料格式),以此保障外掛模組可以在核心繫統上正確執行。外掛模組是獨立於核心系統之外的,但是根據具體的需求(提供單純的資料服務處理系統資料和資訊)可能會需要操作核心模組的系統服務做一些定製化功能,此時核心系統需要提供一個上下文物件(Context),且外掛模組與外部進行互動只能通過此上下文物件。上下文物件提供了基礎操作(調起其他外掛模組、調起系統服務、獲取系統資訊)的API和事件。

4、在前端系統中使用

把前端系統當成一個作業系統,業務基本操作的業務邏輯抽象成一個可以獨立運作的系統核心,而不屬於業務基本操作的業務邏輯都當成一個應用程式,完成安裝、解除安裝、禁用、呼叫以及開機啟動等功能。

在功能越來越多,依賴越來越負責的大型前端系統中,如果在專案初期沒有很好地考慮後期相容的靈活性、擴充套件性以及彈性,很容易出現專案難以維護或者誰都不想碰的尷尬場面,所以初期的設計很重要。

目前的大型前端單頁面系統使用的都是根據業務劃分獨立元件,進行解耦和複用,最後通過元件進行堆疊、編譯、上線。這樣雖然完成依賴良好的元件化設計考慮到了系統的擴充套件性和靈活性以及彈性,但是整個系統還是緊緊綁在一起的,並沒有根據基礎業務和附加業務進行很好的拆分。當然很多優秀的前端工程師也考慮到了這一方面,提出來微前端的概念。不過微前端還是一個比較新的技術概念,沒有經過很多大型前端系統的實踐。而微核心架構已經在作業系統和很多的產品的後端服務及前端APP中經過了很多的實踐。

一、定義核心模組和系統服務

上面提到核心模組是一個可以獨立執行起來,包含系統基本操作規則的最小化模組。沒有任何外掛模組依然可以正常執行並處理基本的業務邏輯,所以在大型前端系統中將基礎頁面以及基礎功能單獨包裝起來,組成一個最小化的模組,稱之為core system。而這個core system可以通過包方式在多個系統間進行復用(NPM、bower、bundle、js chunk)。同時,將那些和業務相關的操作按照型別和場景封裝為多個系統服務,並掛載(依賴注入)到核心系統中,稱之為system service。需要注意的是core system可操作system service,而system service不可操作core system

此外,core system根據具體的模組規範(AMD、CMD、CommonJS、ESM、SystemJS、UMD)向外部暴露了可互動的API和事件,稱之為標準介面。後續在編寫外掛模組時要嚴格按照標準介面進行開發。

二、定義外掛模組

外掛模組是一個獨立於核心系統的專業處理不屬於系統基本操作的業務的模組(元件),比如網盤中的上傳、下載、分享等功能。每個外掛模組必須遵照定義好的標準介面通訊規範進行開發,而且每個外掛模組都是相互獨立的,所以沒有對每個外掛的實現細節做過多要求,如A外掛模組使用React開發,B外掛模組使用Vue開發,C模組使用jQuery開發。

每個外掛模組都應該提供一個包含本外掛模組簽名資訊(Mainfest)的JSON檔案,簽名資訊包括了這個外掛的名稱、資料規範、依賴、遠端訪問地址(非同步載入的js下載地址)和其他自定義欄位。在前端載入核心系統時將該Mainfest檔案註冊進去,完成核心繫統和外掛模組的連線。

每個外掛模組都應該提供一個統一名稱的執行入口,比如start方法。也可以按照標準介面提供外掛的生命週期事件,方便更細粒度的控制。

三、註冊和調起

每個外掛都提供了各自的Mainfest簽名檔案可執行檔案(JS檔案、CSS檔案)。所以當伺服器接收到瀏覽器請求時可以將所要求的外掛Manifest進行merge,合併成一個大的JSON結構,然後返回給瀏覽器。瀏覽器接收後,執行核心系統並註冊Manfiest資訊,然後啟動。在註冊過程中可以按照需求完成開機啟動(預設執行)預載入以及後臺執行等不同型別的操作。

在業務邏輯和外掛內部邏輯中可以能存在調起其他外掛模組的需求,由於外掛模組之間不產生依賴並且獨立於核心系統,所以無法直接進行調起。不過由於登錄檔點對點事件模式的存在,可以通過核心系統向外暴露的API傳入外掛名稱和組外掛模組ID等資訊進行調起。在調起之前先判斷該外掛在是否已註冊,是否已載入(同步載入、非同步載入),是否為單例和互斥,引數資訊和資料格式,保證它可以正確的調起。

在外掛執行過程中出現異常時,通過系統級事件通知核心模組。核心模組根據簽名資訊中的標識選擇重啟關閉該外掛模組。

四、多入口管理

在複雜的前端系統中同一個功能可能會存在過個入口的情況,比如上傳、下載、分享等功能都是通過不同位置的按鈕點選進行調起。通常,將具體功能(外掛模組)外掛模組入口的UI展示進行隔離。首先,在基礎頁面結構中按照需求進行分塊,分成不同的功能塊,如選單欄、右鍵選單、列表項、右側區域、左側區域,併為這些區域定義唯一的名稱和ID。在需要進行入口展示的外掛模組的Manifest中,標識入口的區域和展示方式(按鈕、圖片、引導塊、選單項、下拉選單)。

核心系統在登錄檔註冊完畢後,解析那些需要展示入口的的欄位並交給專門渲染外掛模組入口系統服務,這樣就通過配置完成了多入口的管理,在後續需求變動和修改時,只需要更改Manifest檔案即可,更加完善了系統的擴充套件性、靈活性、彈性。

5、技術選型

架構是獨立於框架和類庫的存在。

微核心架構的核心就是使業務的基本操作和專業處理額外特性的操作相隔離,提高系統的擴充套件性、靈活性和彈性。所以在技術選型時我們需要考慮三個方面:核心系統、系統服務、外掛模組。

核心系統通常包含一個專案所需要的基本功能,包括基本的展示頁面、互動操作、業務處理,程式碼量通常很少;系統服務提供業務處理的通用功能,比如列表操作、彈框、提示、非同步化介面處理等,通常將系統中通用的需求抽象到這一層中;所以,這兩個方面可以使用目前常見的react或vue通過webpack工具進行規範化開發,但如何向外部暴露核心系統的API和事件給外掛模組呼叫是一個十分重要的問題。

外掛模組更傾向於一個專業處理額外特性的lib庫,所以推薦使用rollup或者webpack的lib模式進行開發和打包,產出一個『乾淨』的bundle(也可以釋出到NPM中,實現獨立釋出和維護)。需要注意的是,如果這個bundle按照定義好的標準規範進行開發,那麼它可以在任意一個微核心架構下執行,達到跨系統的能力。就像按照X86規範編寫的程式可以在任意一個X86架構的系統上執行一樣。

調起外掛模組時如何非同步載入外掛模組bundle?

一、外掛模組開發階段

方案一:source code
外掛模組的程式碼放置在一個根資料夾中,通過原始碼進行開發和編譯。每次更改後通過rollup或webpack產出一個bundle與Manifest檔案,然後將它們上線更新即可。

這種模式下,外掛模組的程式碼更新後,對應的Manifest檔案也會更新,所以核心系統載入到外掛模組也會被更新,不需要基礎業務邏輯執行任何操作。
優點:不需要更新並上線基礎業務程式碼。
缺點:沒有版本號的管理功能以及不方便測試。

方案二:npm install
外掛模組釋出到github、gitlab等其他託管平臺中,通過npm進行安裝到基礎業務邏輯中。外掛模組每次更改後需要重新發布到託管平臺,並在需要在業務邏輯中更新版本號重新執行npm install xxx,然後重新編譯業務程式碼進行上線。

外掛模組更新後,不需要像方案一那樣上線外掛模組。而是更新業務邏輯的依賴,安裝最新版本的外掛模組。
優點:可以通過版本號載入不同的階段的外掛模組以及方便測試。
缺點:更改後需要重新安裝外掛模組,並對依賴此外掛模組的業務邏輯重新進行編譯和上線。迴歸成本大,除了迴歸外掛模組還要回歸其他基礎業務邏輯(當然也可以像方案一那樣做,但是這樣就拋棄了npm的最大優點 -> 版本號管理)。


二、獲取外掛模組的Manifest簽名資訊

方案一:伺服器渲染直出到HTML中
伺服器收到瀏覽器的頁面請求時,將該頁面需要的外掛模組的Manifest簽名檔案進行Merge操作,然後統一輸出到HTML中並返回給瀏覽器。

方案二:通過非同步化獲取
通過script標籤的async和defer功能或AJAX,非同步從伺服器獲取Merge之後的Manifest簽名資訊集合。


三、遠端訪問協議

核心系統調起外掛模組時,可以通過外掛宣告的遠端訪問協議的HTTP地址,進行非同步載入。

方案一:Manifest簽名檔案
在Manifest簽名資訊中放置外掛模組的遠端訪問協議,比如上傳外掛模組的簽名示例:

{
	// 外掛名稱
    "name": "upload",
    // 組
    "group": "com.xxx.xxx",
    // 預載入外掛模組資源
    "preload": true,
    // 資料規範,要求輸入的引數
    "arguments": {
	    // 核心系統提供的上下文物件
        "ctx": {
            "type": "Object",
            "required": true
        },
        // 需要上傳的檔案資訊
        "file": {
            "type": "Object",
            "required": false
        }
    },
    // 遠端訪問協議
    "entrance": "http://www.a.com/static/plugin-bundles/upload-0.0.1.min.js"
}
複製程式碼

方案二:非同步化介面 + import()

該方案是系統外掛模組的遠端訪問協議不放置在外掛模組的Manifest中,而是額外通過非同步化介面請求得到遠端訪問協議。然後通過webpack提供的require.ensure()或esm的import()載入外掛資源。

// ctx為核心系統上下文物件
ctx.loadPlugInAdapter = (pluginName, groud) => {
	// 通過介面請求上傳外掛模組的遠端訪問協議
    fetchEntrance(pluginName, group).then(url => {
	    // 核心系統執行外掛模組
	    ctx.invoke(pluginName, url);
    });
}

// 調起外掛模組
ctx.loadPlugInAdapter(`upload`, `com.xxx.xxx`);
複製程式碼

最後

架構和框架是獨立的,本文僅僅是提出一種架構思路,而且這個架構也在百度的某款使用者量很大的複雜前端產品中得以應用。基於這一套彈性架構並結合Vue/React的現代化開發理念,可以很好的完成高複雜度的前端系統。希望本文可以給你們提供了除微前端之外的構建高彈性前端系統的另外一種思路。

相關文章