摘要:插拔式應用架構方案和傳統前端架構相比有以下幾個優勢業務模塊分布式開發,代碼倉庫更易管理。
背景
隨著互聯網云的興起,一種將多個不同的服務集中在一個大平臺上統一對外開放的概念逐漸為人熟知,越來越多與云相關或不相關的中后臺管理系統或企業級信息系統曾經或開始采用了這種「統一平臺」的形式。同時,前端領域保持著高速發展,早期的 jQuery+Backbone+Bootstrap 的 MVC 解決方案支撐起了業務相當長的一段時間;后來,Angular、Ember 等 MVVM 框架開始嶄露頭角,前后端分離和前端組件化的思想在此時達到了鼎盛期。而在國內,Vue 框架憑著其簡潔易懂的 API 和出色的周邊生態支持獨領鰲頭,越來越多的中小型企業和開發者們開始轉向 Vue 陣營;與此同時,在設計上獨樹一幟的純 View 層框架 React 開始興起,其充滿技術感的 Diff DOM 思想吸引了大批開發者,成為各大技術社區最火爆的話題,其周邊生態也隨之快速發展,成為了各大公司搭建技術棧時的首選框架。
回到平臺的話題。一個集成了不同業務的大平臺,很多情況下都是將業務拆分成多個子系統進行開發,最后由平臺提供統一的入口。而在當前快速變化的前端大環境下,此類平臺需要考慮以下幾個難題:
怎樣將不同業務子系統集中到一個大平臺上,統一對外開放?
如何給不同用戶賦予權限讓其能夠訪問平臺的特定業務模塊同時禁止其訪問無權限的業務模塊?
如何快速接入新的子系統,并對子系統進行版本管理,保證功能同步?
針對于老系統,如何實現從 Backbone 技術棧到 React 技術棧或 Vue 技術棧的平滑升級?
接下來,我將分別基于這幾個問題介紹我們的實現方案。
產品模型首先我們來討論第一個問題:怎樣將不同業務子系統集中到一個大平臺上,統一對外開放?
如下圖所示,假設我們有三個業務子系統,用戶如果要使用三個系統中的不同功能,他就需要同時在三個系統中登錄然后來回切換進行操作。
而實際上理想的狀態是:A、B、C 三個子系統在同一個大平臺上,通過菜單提供入口進入,用戶可以自由訪問任意一個子系統的頁面。如下圖所示:
注意到上圖中我們給 A、B、C 都標記了 App(Application),把大平臺標記為了 Product,以下為了方便說明,我們把每個子系統都稱為 App,把集成子系統的平臺稱為 Product。
事實上,對于真正的業務場景,除了用戶體驗的改善,圖 2 所示系統還有很多優勢,比如果企業想按業務模塊售賣產品,第二種方式顯然更好,用戶支付模塊費用后賦予其模塊權限就可以使用新模塊了,而不是提供給用戶一個新系統。除此以外,對企業來說避免部署獨立的業務系統也就意味著省掉了域名、服務器、運維方面的資源,節省了企業成本。
架構方案確定了 Product 包含 App 的產品模型后,我們接下來要考慮以怎樣的一種形式,讓每個 App 的訪問都能夠在 Product 下實現無縫切換。
如下圖所示,在訪問頁面時,我們為訪問路徑附加上了應用前綴,標識當前訪問的是哪個 App,App 路徑前綴之后才是當前訪問的頁面路徑,這是一個前提約定。
而從 Product 角度來看,我們希望用戶在使用平臺時,感受不到各個 App 在切換時是在切換各系統模塊,所以 Product 需要控制所有 App 的視圖渲染時機,即:Product 需統一管理所有 App 的視圖路由。
同時,為了給不同權限用戶展現不同的視圖頁面,我們把從后端返回的用戶權限數據也傳入 Product,Product 會自動過濾掉沒有權限的路由,如下圖所示:
這里,因為需要讓各 App 之間的切換對用戶來說就如同切換一個系統應用的各個頁面,我們采用了單頁面應用(SPA)的形式實現 Product 的路由控制。
整個方案的架構如下圖所示:
在這個架構方案下,各子業務模塊可以根據需要動態加入大平臺下,不需要時屏蔽訪問路徑前綴即可;對平臺系統而言,各子業務模塊如同一個個功能插件,即插即用,不用即拔。這種插拔式的思想由來已久,我們稱之為「插拔式應用架構」。插拔式應用架構方案和傳統前端架構相比有以下幾個優勢:
業務模塊分布式開發,代碼倉庫更易管理。
業務模塊(App)移植性強,可多帶帶部署,也可整合到大平臺(Product)下。
模塊代碼高內聚,更專注業務。
符合開閉原則,新模塊的接入不需要修改已有模塊,不會影響其他模塊的功能。
資源權限管理在介紹架構方案的具體實現之前,我們需要先做些準備工作,先來看下開頭我們提出的第二、三兩個問題。
首先是第二個問題:如何給不同用戶賦予權限讓其能夠訪問平臺的特定業務模塊同時禁止其訪問無權限的業務模塊?
上文中簡單提到了后端將訪問權限數據傳入 Product,我們的具體做法是每個 App 將自己的全量路由路徑傳入 Product ,而在啟動平臺(Product)時,Product 會從后端根據當前登錄用戶獲取其有權限的路由路徑,當訪問 App 任一路由時,會在首次與有權限的路由路徑進行比對,比對失敗的路由路徑會自動導向無權限的頁面視圖。
至于路由的權限維護,可以做一個可視化配置路由的管理頁面,權限的細化程度根據自己的業務情況自定義即可。
其次是第三個問題:如何快速接入新的子系統,并對子系統進行版本管理,保證功能同步?
要回答這個問題,我們就要清楚每個 App 具體的接入方式。上文中有提到每個 App 的訪問依賴于當前的路徑前綴,我們的具體做法是后端維護所有 App 基于 webpack 打出的 bundle 包的地址,并將這些包地址的配置映射關系傳入 Product,當首次訪問到某個 App 時,Product 會首先加載該 App 相關的 bundle 包,而其 js bundle 包內會調用全局的 Product 注入自己的路由信息,然后將后續的路由處理交給 Product 執行。
當然,上述的實現會涉及到渲染 App 視圖時的一些問題,在接下來的實現方案中我們會介紹到。
實現方案上面我們討論了很多理論性的內容,接下來進入干貨環節:如何實現一個插拔式應用框架?
根據上文中介紹一些實現思路,我們對將要實現的插拔式框架會先有一個大概的功能輪廓:
自實現一個 Router,該 Router 需要在路由時根據路徑自動解析出 App 標識,然后基于標識動態加載 App 對應的資源包。
App 加載其 js 資源包后立即執行,自動向 Product 內注入 App 相關的路由信息。
Router 在 App 加載完資源包后(script 腳本會在加載后立即執行),嘗試根據路徑渲染 App 視圖頁面。
切換路由后,如果切換至了其他子 App,原 App 應基于自身的生命周期,清除相關 DOM 和事件等邏輯。
簡單歸納一下,我們的插拔式應用框架應在實現上做出以下幾個功能點:動態路由、腳本加載和調度、子應用視圖渲染、應用生命周期管理。
接下來我們分別一一介紹各功能點的實現思路。
動態路由說起路由,對于不同的技術棧,有著不同的實現方案。如 Vue 有 vue-router,React 有 react-router 等。而為了適配各子 App 采用不同的技術體系開發的情形,我們需要將路由配置加以規范和統一管理。所以,我們需要重新設計一個 Router,這個 Router 必須能夠做到:動態注入路由且同時支持不同技術體系組件的渲染。
這里,我們采用了靈活性較強的 universal-router,其 path 和 action 的配置方式能夠讓我們很方便地進行自定義的路由邏輯處理。雖然它不支持動態注入路由,但其代碼組織合理,配合大名鼎鼎的 history 庫,我很容易便實現了滿足自己需求的 Router。
如下圖所示:
腳本加載和調度在完成動態路由的基本功能后,我們就要開始處理路由邏輯的第一步了:動態加載當前訪問 App 的腳本等資源包。
首先我們先分析出處理流程:在開始路由時,我們需要根據請求路徑的第一段路徑名(如 /a/b 的第一段為 a)確定當前要路由的路徑對應的是哪一個 App,若對應的 App 尚未注入路由信息,就需要動態加載 App 的資源包,待執行了 js 腳本資源包后,再繼續執行后續的渲染邏輯。
App 的資源包可以有多種形式的打包方式,如 AMD、Commonjs、UMD 等。而為了兼容 App 能夠分別多帶帶部署和集成至平臺兩種情況,且保持最簡化的依賴,我們仍舊采用基于 webpack 打出 UMD 包的形式——讓 JS 加載后立即執行即可,省去了如對 AMD 包加載器如 Requirejs 的依賴。
那么,依托于瀏覽器自身的腳本加載機制,我們的資源包加載器就很好實現了:分別使用 link 和 script 標簽在 head 和 body 標簽下動態插入資源包地址即可。
當然,也有人會考慮到資源包先后順序加載依賴的問題。一般情況下,webpack 打包時會自行處理依賴關系,如果對多個資源包插件有先后執行順序的依賴需求(如 jQuery 插件依賴),可在加載時做特殊的串行處理。
App 腳本加載流程如下圖所示:
應用視圖渲染處理了 App 資源包的動態加載后,我們就要實現路由模塊最核心的功能了:應用視圖的渲染。
首先,在上文介紹方案時,我們提到每個子 App 既要能支持多帶帶部署,又需要能夠接入 Product 內,在平臺上運行。所以,我們應該意識到:各 App 視圖的渲染應該交由每個子 App 自己完成,而不是由框架統一完成。
如果你對上面的結論感覺太突兀,那么,請思考以下兩個問題:
如果框架統一渲染路由結果,那么如何保證對 React Component、Backbone View 等各種不同形式組件的兼容?
如果框架統一渲染路由結果,就需要引入渲染接口,那么如何保證兼容各子 App 的接口版本(如 ReactDOM 版本等)?
所以,為了體現框架兼顧不同技術體系 App 的插拔式設計思想,我們必須要將應用視圖的渲染從框架內抽離出去。
那么,框架的路由在視圖渲染邏輯上還需要做什么事呢?
我們很快就會想到視圖渲染邏輯抽離出去后存在的問題:各子 App 要自己實現渲染了,那框架提效的作用體現在了何處?渲染接口又該如何統一?
前文中提到了開閉原則,開閉原則最主要的設計思想就是面向對象設計。我們的解決方案就是:
提供一個 Application 基類,規范渲染接口,各子 App 在注入應用時必須注入繼承自 Application 基類的應用實例。
默認提供使用較廣的 React Application 和適用性較強的 Backbone Application 兩個渲染實現應用類(均繼承自 Application 基類)。
在各子 App 的入口 JS 文件內,可以根據自己的技術體系直接實例化 ReactApplication 或 BackboneApplication,也可以繼承自 Application 基類自實現渲染接口。當然,如果自己的應用類使用較多,可以作為插件貢獻出去。
Application 基類的示例代碼:
// application/index.js class Application { static DEFAULTS = { // ... } constructor(options = {}) { this._options = Object.assign({}, DEFAULTS, options); } start() { // 啟動應用,開啟 view 的路徑變化監聽事件 } stop() { // 停止路徑變化監聽事件 } renderLayout() { // 渲染布局的接口 } render() { // 渲染主體內容的接口 } // ... }
ReactApplication 類的實現示例代碼:
// application/react/index.js import Application from "../index.js"; class ReactApplication extends Application { render(err, children, params = {}) { if (err) { // 渲染錯誤頁 throw err; } // React 和 ReactDOM 在實例化時由 App 自己傳入,便于各 App 自己控制 React 版本 const { React, ReactDOM } = this._options; ReactDOM.render(children, this._container); } }
BackboneApplication 類的實現示例代碼:
// application/backbone/index.js import Application from "../index.js"; class BackboneApplication extends Application { render(err, viewAction, params = {}) { if (err) { // 渲染錯誤頁 throw err; } if (viewAction.prototype && isFunction(viewAction.prototype.render)) { this._currentView = new viewAction(params); return this._currentView.render(); } if (typeof viewAction.render === "function") { return viewAction.render(params); } } }
將渲染邏輯交給各子 App 自己實現后,我們就可以避免在框架的 View 類中根據不同技術體系實現不同的渲染邏輯。如果子 App 換了 Backbone 和 React 之外的其他渲染方式,我們也不必修改框架的實現重新發布新的版本。
另外,除了應用實例外,我們還需要構造一個 Product 類,提供注入應用實例的入口。示例代碼如下:
class Product { static registerApplication = (app) => { // 緩存 app 實例,并注入 app 路由 } }
在各子 App 的入口 JS 文件內,調用 Product 類注入當前 app 實例(以 React App 為例):
// src/app.js import React from "react"; import ReactDOM from "react-dom"; import { Product, ReactApplication } from "plugin-pkg"; const app = new ReactApplication({ React, ReactDOM, // ... }); Product.registerApplication(app);應用生命周期管理
到這里,從動態路由到視圖渲染,我們都已經有了具體的實現思路,現在考慮實際應用時的一個問題:在切換各子 App 時,上一個 App 的 DOM 會被替換,但相關的事件并未正確清除。拿 React 來說,我們直接替換掉 DOM 內容,但未正確觸發 React 組件的 UnMount 事件,Backbone View 的 destroy 回調同理。
所以,我們需要為 Application 類添加 destroy 接口:
class Application { destroy() { // 在當前 App 實例切換出去時調用 } }
除了銷毀事件,有時在 App 切換進來后也會需要一些統一處理,我們同時需要添加 ready 接口:
class Application { ready() { // 在當前 App 實例切換進來時調用 } }
生命周期的處理實現,各 App 實例根據自己的實際情況自行實現相關邏輯即可。
框架在切換 App 時,需自動調用上一個應用實例的銷毀接口,然后在渲染 App 后,再自動調用當前 App 的準備接口。
構建配置上面的內容都是插拔式框架需要實現的功能,另外,各子 App 在打包時也要統一配置。如框架的依賴應設為 external 的形式,在打包時不打入資源包。因為我們的各 App JS 資源包都是 UMD 包直接執行的形式,在實際運行時使用 Product 統一引入的框架包的全局變量即可。
webpack 配置的示例代碼如下:
// webpack.config.js const path = require("path"); const resolveApp = relativePath => path.join(process.cwd(), relativePath); module.exports = { entry: { bundle: resolveApp("src/app.js"); }, module: { // ... }, plugins: [ // ... ], externals: { "plugin-pkg": "Plugin", }, };
這樣,不但能兼容獨立部署和集成入平臺兩種形式,也能在插入平臺模式下統一用平臺的插拔式框架包,便于平臺的統一升級。
總結以上的插拔式應用設計是因為考慮到了兼容不同技術體系的子業務模塊,路由的實現稍顯繁復,腳本的動態加載也比較簡單。在實際業務需求中,如果已經確定了統一技術體系,大部分情況下就不必考慮兼容不同子業務模塊的問題了,完全可以選定一種技術體系(如 Vue 或 React)來實現,多做的可能也只有權限處理這一小塊。
所以,以上內容僅作參考,根據實際業務不同,設計出適合自己業務的插拔式方案,才是最好用的方案。
參考single-spa
文章可隨意轉載,但請保留此 原文鏈接 。
非常歡迎有激情的你加入 ES2049 Studio,簡歷請發送至 caijun.hcj(at)alibaba-inc.com 。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108310.html
摘要:月日,在中國信息通信研究院中國通信標準化協會聯合主辦為期兩天的可信云大會上,主辦方頒發了年上半年可信云系列評估認證,以及公布了可信云相關技術服務能力與應用案例最佳實踐評選活動榜單。7月27日,在中國信息通信研究院、中國通信標準化協會聯合主辦為期兩天的2021 可信云大會上,主辦方頒發了2021年上半年可信云系列評估認證,以及公布了可信云相關技術、服務能力與應用案例最佳實踐評選活動榜單。UCl...
摘要:按鈕方面按鈕通過自定義指令綁定其特定的操作接口信息如產品上傳按鈕,需要擁有產品上傳的信息,才可以繼續執行按鈕的業務邏輯。 開篇啰嗦幾句 在傳統單體項目中,通常會有一些框架用來管理熟知的權限。如耳濡目染的 Shiro 或者 Spring Security 。然而,到了現在這個時代,新開始的項目會更多的才用后端微服務 + 前端 mvvm 的架構開始書寫項目。權限控制方面將變得有些許晦澀。當...
摘要:而從技術實現角度,微前端架構解決方案大概分為兩類場景單實例即同一時刻,只有一個子應用被展示,子應用具備一個完整的應用生命周期。為了解決產品研發之間各種耦合的問題,大部分企業也都會有自己的解決方案。 原文鏈接:https://zhuanlan.zhihu.com/p/... Techniques, strategies and recipes for building a modern ...
摘要:什么是單頁面應用單頁面應用是指用戶在瀏覽器加載單一的頁面,后續請求都無需再離開此頁目標旨在用為用戶提供了更接近本地移動或桌面應用程序的體驗。流程第一次請求時,將導航頁傳輸到客戶端,其余請求通過獲取數據實現數據的傳輸通過或遠程過程調用。 什么是單頁面應用(SPA)? 單頁面應用(SPA)是指用戶在瀏覽器加載單一的HTML頁面,后續請求都無需再離開此頁 目標:旨在用為用戶提供了更接近本地...
閱讀 2595·2021-11-17 09:33
閱讀 3936·2021-10-19 11:46
閱讀 910·2021-10-14 09:42
閱讀 2252·2021-09-22 15:41
閱讀 4204·2021-09-22 15:20
閱讀 4628·2021-09-07 10:22
閱讀 2302·2021-09-04 16:40
閱讀 811·2019-08-30 15:52