Uber 開源 Fusion.js:一個基於插件架構的通用 Web 框架

Uber 開源 Fusion.js:一個基於插件架構的通用 Web 框架


可能很多人都不知道,Uber 其實開發了很多基於 Web 的應用程序,可能有數百個,而且這個數字還在不斷增加中,它們中的大部分被用在公司內部,用於管理各種業務。

我們知道,Web 技術變化得很快,而最佳技術實踐也在不斷髮展。為數百名 Web 工程師提供高質量的框架和功能,同時又要保持 Web 平臺的動態特性,一直以來都是一個巨大的挑戰。

為了應對這一挑戰,Uber 的 Web 平臺團隊開發了 Fusion.js,一個開源的 Web 框架,用於簡化 Web 開發,並構建出高性能的輕量級 Web 應用程序。

動機

隨著 Web 最佳實踐的發展,Uber 需要改造已有的單體 Web 框架,解決長達數年的技術債務所帶來的挑戰。我們還希望讓工程師們能夠繼續使用他們喜歡的技術(例如 React 和 Redux),同時保持與 Uber 健康監控基礎設施的兼容性。

具體來說,我們希望核心框架能夠解決以下痛點:

  • 服務器端渲染、代碼拆分和模塊熱加載所需的複雜配置和工具樣板代碼
  • 在涉及服務器端渲染的 React 應用程序時,缺乏用於實現和共享特性的良好抽象
  • 不同位置的代碼緊密耦合而導致的脆弱性
  • 測試難度攀升
  • 單體框架​​缺乏靈活性

雖然現有的解決方案解決了其中的一些挑戰,但我們發現,基於框架添加新庫通常需要修改多個不相關的文件。例如,要讓可進行服務器端渲染的應用程序支持 Redux, 通常需要在服務器相關的文件中添加代碼,並在客戶端添加類似的代碼,還要向 HTML 模板中添加 hydration 代碼,使用 React Provider 組件等。要集成 i18n 庫或添加瀏覽器性能指標庫也是一樣。

很多特定於應用程序的代碼可能會依賴用於管理副作用的庫(例如用於日誌記錄或數據持久化的庫),工程師很難在沒有服務層抽象的幫助下以可測試的方式集成這些庫。

我們既希望能夠為與 Uber 現有庫集成提供簡單且經過實戰考驗的解決方案,也希望能夠避免使用單體框架,從而保持捆綁包的小體積。

我們傾向於選擇模塊化方法的另一個原因是,我們必須明確指定依賴關係,這樣可以更容易避免技術債務,如 God Object(https://en.wikipedia.org/wiki/God_object)、臨時內部接口和緊密耦合。

Fusion.js 是我們努力的結晶。

誰應該使用 Fusion.js?

簡單地說,Fusion.js 是一個 MIT 許可的 JavaScript 框架,支持 React 和 Redux 等流行庫,並提供了很多現代特性,如模塊熱加載、數據感知服務器端渲染和捆綁拆分支持。

除了預配置的樣板,Fusion.js 還提供了靈活的基於插件的架構。因此它非常適合用於現代單頁應用程序以及依賴複雜服務層來滿足各種質量要求的現代 Web 應用程序。

有關 Fusion.js 的更多信息,請查看項目文檔。

基於插件的架構

Fusion.js 應用程序是通用的,也就是說它有一個單入口文件,並且可以在服務器和瀏覽器上重用代碼。在通用的應用程序中,React 組件還可以獲取數據並在服務器上渲染 HTML,從而可以利用瀏覽器的原生 HTML 解析器和避免 JavaScript DOM API 的開銷來減少頁面加載時間。

單入口架構使 Fusion.js 插件本身也具有通用性,插件開發人員可以將代碼片段與代碼所屬的庫放在一起,而不是與代碼運行的環境放在一起。

Uber 開源 Fusion.js:一個基於插件架構的通用 Web 框架


Fusion.js 插件基於邏輯分組封裝邏輯,而不是基於需要添加代碼的位置

插件可以通過中間件訪問 HTTP 請求生命週期,也可以訪問 React 樹,以便添加 Provider 組件。它們還可以初始化瀏覽器代碼。

最後,由於這些特性,我們可以通過單行代碼將庫安裝到應用程序中,無論庫需要多少個不同的集成點。由於插件易於添加和刪除,因此在重構時也很容易推斷它們之間的耦合度、對包大小的影響以及其他代碼質量屬性。它們也可以初始化瀏覽器代碼。

類型依賴注入

插件利用了依賴注入,這意味著它們可以將定義良好的 API 作為服務暴露給其他插件,並且在測試期間可以輕鬆地模擬插件的依賴項。當依賴關係負責與數據存儲基礎設施打交道或與可觀察性(例如日誌記錄、分析和指標)相關時,這一點尤為重要。

複製代碼

/*
這個例子實現了一個從 session 讀取數據的端點。
Session 是通過依賴注入的方式提供的。
SessionToken 是一個標籤 (用於確保類型安全)。
*/

// src/plugins/user.js
import {createPlugin} from 'fusion-core';
import {SessionToken} from 'fusion-tokens';

export default __NODE__ && createPlugin({
deps: {Session: SessionToken},
middleware({Session}) {
return async (ctx, next) => {
if (ctx.path === '/api/user') {
ctx.body = JSON.parse(await Session.from(ctx).get('user'));
}
return next();
}
}
});

還可以藉助 Flow.js 來確保依賴之間的靜態類型安全,如下所示:

Uber 開源 Fusion.js:一個基於插件架構的通用 Web 框架


直接在代碼編輯器中顯示錯誤有助於在代碼運行之前捕獲錯誤

中間件管理

幾年前就存在這樣的一個挑戰,流行的 HTTP 服務器庫 Express 有一個 API 讓複雜的響應轉換變得難以封裝和測試。在 Uber 以前的架構中,應用程序開發人員經常需要採用臨時包含 Express 請求 / 響應對象的特定補丁。自然而然地,因為子系統對時間要求的高度耦合,測試變得極其困難。

在開始設計 Fusion.js 時,我們就一直關注這個問題。經過大量調研,我們決定使用 Koa(https://koajs.com),它提供了基於上下文的 API,對單元測試非常友好,併為請求生命週期管理提供了一個基於下游和上游概念的輕量級抽象。

事實證明,採用 Koa 是一個正確的設計決策。

Koa 中間件為 React Provider 組件提供邏輯集成點,下游 / 上游抽象與 React 服務器渲染上下文的生命週期完美匹配。網絡副作用與應用程序邏輯分離,從而提高了可測性。

Fusion.js 的依賴注入和圖解析機制解決了困擾我們已久的 God Object 和操作順序問題。

Uber 開源 Fusion.js:一個基於插件架構的通用 Web 框架


Fusion.js 核心將網絡副作用與應用程序狀態隔離開來,並利用 Koa 和 DI 來實現子系統之間的鬆散耦合

可測性

在過去的幾年裡,JavaScript 生態系統中出現了大量高質量的測試工具,並提高了對測試技術的認識。

除了支持 Jest、Enzyme 和 Puppeteer 等現代測試工具外,Fusion.js 還為開發人員提供了測試插件的工具。fusion-test-utils 包允可用於模擬服務器本身,從而可以在插件和各種樁的組合上快速運行集成測試。

前行之路

在 Uber 內部,已有 60 多個項目代碼庫在使用 Fusion.js。我們預計這個數字會繼續增加,因為新的 Web 項目也在不斷創建,同時舊項目被自動遷移到 Fusion.js。因此,框架級別的改進應該能夠顯著改善這些項目的軟件質量基準。

我們的路線圖包括添加更多的性能優化和麵向測試的工具,以及更好的 Flow 支持。

項目地址:https://github.com/fusionjs

查看英文原文:https://eng.uber.com/fusionjs/


十年顛覆,Uber終於成功上市,這被認為是繼Facebook、阿里巴巴之後最具價值的科技公司IPO之一,也是今年上市的硅谷“獨角獸”中的一員。十年時間,Uber從虧損40億到營收百億美元,業務從美國到遍佈五大洲,月活從0到9100萬人次,支撐其快速成長背後的技術力量值得探尋。在過去10年,InfoQ對Uber的技術實力進行了全方位報道,本文是InfoQ出品的《Uber上市背後的技術力量》專題中的一篇文章,還有更多精彩內容,歡迎點擊瞭解更多查看

Uber 開源 Fusion.js:一個基於插件架構的通用 Web 框架

相關推薦

推薦中...