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

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

只討論架構,不討論框架

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的現代化開發理念,可以很好的完成高複雜度的前端系統。希望本文可以給你們提供了除微前端之外的構建高彈性前端系統的另外一種思路。

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