當前位置:
首頁 > 科技 > FreeWheel前後端分離改造實踐

FreeWheel前後端分離改造實踐

在現代前端應用的工程實踐中,前後端分離的架構會為兩端帶來更多的靈活性,已成為主流趨勢。與之相對的,傳統的單體 Web 應用(Monolithic Web Application)則將前後端代碼放在一起,雖然耦合性較強,但在產品研發的特定階段仍具有較強的優勢,早期的 ASP.NET、Java Spring MVC,以及 Ruby On Rails 都是單體應用的代表性框架。

本文將以 FreeWheel 從單體應用改造為前後端分離的實踐為例,著重介紹其間前端所遇到的挑戰和解決方案。

相較消費者,商業用戶對前端應用的需求更具複雜性,且更強調質量。FreeWheel 深耕企業級的視頻廣告領域 10 年,其基於 Ruby On Rails 框架為廣告主打造的 Web 管理應用已經歷多輪迭代和演進,目前已達到 20 多個產品模塊,1200+ 頁面,代碼量已達到 143.5 萬行代碼,其中包含 39 萬行基於 jQuery 的傳統 JS 代碼。為保證其質量,其中包含了 20.2 萬行單元測試代碼,除此以外,還有獨立的近 2 萬個自動化測試腳本。在兩年前,我們感受到了單體應用的局限性,並決定將其改造成為前後端分離的架構。

技術選型

FreeWheel 前端展現的業務多種多樣,但其用戶體驗強調高效性和高一致性。為輔助業務研發團隊進一步提升前端開發的效率和效果,我們在改造前期訂立了組件化的目標,力求將統一的用戶體驗和複雜的內部交互邏輯封裝進組件,通過自動化測試保證其質量,並最終在業務模塊中廣泛復用。

針對以上目標,我們選擇 React 作為新前端核心技術,以 ES6 作為開發語言,利用 Webpack 和 Babel 進行編譯打包。以 Mocha 全家桶加 Enzyme 作為單元測試框架保證組件質量。每個業務模塊均開發、打包並發布為一個獨立的 SPA(Single Page Application,單頁應用),多個模塊 SPA 之間,除了以統一的 SSO 服務保證用戶認證外,並無更多的耦合,這一點保證了多個業務模塊團隊的工作不會互相制約。

在單向數據流框架選擇上,我們基於 Facebook 的 Flux 推進了相當長的一段時間。在上線兩個業務模塊後,我們認識到 FreeWheel 的業務對前端數據流需求的複雜度遠高於常見的 TodoMVC 樣例,Flux 實現這些需求時會遇到較多困難。我們評估了當時的社區新秀 Redux,它能一定程度緩解我們遇到的問題但仍有局限性。我們最終決定以 Redux 和 ImmutableJS 為基礎,開發一套新的單向數據流框架 spark-modula。這點在下一節會有詳細描述。

類似的還有前端路由。我們初期的選型是 react-router,隨後根據項目需要開發了新的前端路由框架 spark-router。

更詳細的前端框架選型如下:

至於後端,我們的選型是以 Golang 開發的微服務。藉此契機,團隊將原來內置於單體應用中的後端服務重新做了一次梳理,並逐步重構成微服務架構中的若干個微服務。前端在通過 SSO 驗證後,以 JSON 格式與微服務交換數據。這些微服務除了滿足前端使用,也會通過 gateway 作為 API 暴露給我們的客戶,更會為公司內部的其他微服務提供基礎。後端架構不是本文重點,故不贅述,有興趣的讀者請參見 FreeWheel 發表的其他文章。

新輪子 SparkUI

為了推進前後端分離改造,我們成立了一個專門的前端小團隊,與業務模塊開發團隊緊密合作,經曆數十個迭代,開發並完善了一套基於 React 的前端框架,內部名稱為「SparkUI」(這一名稱與 Apache Spark 或 Java Spark 無關)。下圖是 SparkUI 框架的簡要架構:

其中上游的 React、Redux、ImmutableJS 等框架為 SparkUI 的直接依賴,下游的 Business Components 業務組件、Business Modules 業務模塊則為基於 SparkUI 框架開發業務代碼的產出;銜接上下游的,則是 SparkUI 的核心組成部分。

可重用組件 Library Components

SparkUI 截止至截稿日已積累了 40 個子 package,其中很大一部分為可重用的 UI 組件,我們稱之為 Library Components,例如 spark-loading、spark-calendar、spark-raw-grid 等。凡是業務模塊提出的對前端組件的需求,只要與業務並不直接相關的,我們都會設計并迭代開發相應的可重用組件。

我們在設計可重用組件時,遵循的一些要點包括:

無狀態組件(Stateless Component)優於狀態化組件(Stateful Component);

組合組件(Composing Components)優於具有 DSL(Domain Specific Language)屬性的單一組件;

高階組件(HOC,Higher-Order Component)優於混合屬性(Mixins)。

應用狀態管理框架 spark-modula

上一節提到 Flux 所提供的單向數據流不能完全滿足我們的業務需求。我們在對比了 Flux 和 Redux 後,決定自主開發一隻新輪子。當時面對的挑戰包括但不限於:

在 Ruby On Rails 應用中,開發團隊曾設計並開發了大量的 Model,這不僅是因為要遵守 RoR 的 MVC 實踐,更是因為業務的複雜程度客觀要求有完整的建模,並基於模型推進前端的開發。我們的新輪子需要以類似的方式來消化業務的複雜性;

在業務的前端需求中,常常有一個頁面內包含 2 個甚至多個 Grid,這些 Grid 之間會互相影響。比如一個典型場景:「Grid A 在自動載入後,如果只包含一條記錄,則自動選中這條記錄,並按該記錄 ID 讀取 Grid B」。這樣的交互在 React 社區被稱為副作用即 Side Effects。我們的新輪子需要用相對簡單的方式支持 Side Effects 的處理。

這隻應用狀態管理的新輪子我們起名為 Modula,併入 spark-modula 包。經過快速迭代,Modula 框架已正式替代早期的 Flux,應用於業務模塊開發。Modula 包括 Model 模型、Constants 常量、Container 容器、Test Utility 測試工具四個組成部分,其中 Model 包含 Props/Hierarchy、Context、Sender/Receiver、Delegates、Bubble Event、Lifecycle Methods、Services、Local Props 等概念/API。以下是一個典型的 Model 例子:

可以看出上半部分相當於 Model 的 schema,Props/Hierarchy/Context 基於 Immutable 數據結構實現了數據模型;而下半部分相當於 Model 的行為,Sender/Receiver + Modula Container 實現了單向數據流。

Modula 框架基於 Redux 但並不限於 Redux,與部分 Redux 生態(如 redux-devtools)兼容,且已完整封裝並隱藏了底層的 Redux。

關於 SparkUI 更完整的介紹,請參見後續更詳細的文章。

前後端整合

從單體應用改造成前後端分離的架構後,理想狀態下,前後端可以分別獨立開發、測試、部署,然而若想實現整體業務,則需要將前後兩端整合。本節將介紹我們開展改造工作以來,在前後端整合領域積累的部分最佳實踐。

RESTful 介面

後端介面均按照社區 RESTful 介面標準定義:

語義化 URL,活用 GET/POST/PUT/DELETE 四種 HTTP 方法;

支持 JSON 與 XML 兩種數據呈現格式,默認情況下,HTTP 請求和響應均使用 JSON,加入 XML 參數,請求和響應改為使用 XML;

優先使用 HTTP 狀態碼(Status Code)表現後端成功狀態或各類常見錯誤,如 HTTP 200(OK)、401(Unauthorized)、422(Unprocessable Entity) 等;

統一業務錯誤碼和錯誤消息;

以 ISO 8061 標準輸入輸出日期時間,如:2015-09-08T01:55:28Z。

在前端我們基於瀏覽器 fetch 介面,封裝了 spark-fetch 包,提供如下功能:

瀏覽器 fetch 的所有功能;

JSON 序列化、反序列化;

為 HTTP 錯誤統一顯示對話框,其中 401 狀態會跳轉至登錄頁面;

根據用戶需要緩存特定資源;

防止 Cross-Site Request Forgery (CSRF)。

我們為前端開發了一套簡單的 Discover 服務發現,以 key-value 方式描述前端中會用到的 RESTful 服務,spark-fetch 包在發起 HTTP 請求時只要傳入 key 和相關參數即可。目前主要用來防止前端代碼里 hard-code 服務 URL,之後會與整個公司級別的服務發現整合起來。

除此之外,我們還在後端開發了一套 API Gateway,提供認證(authentication)、限流(throttling)、跨域等公共功能。上述 RESTful 介面本身無須處理認證等邏輯。在部署後端服務後,只有 API Gateway 開放給外網訪問,其他 RESTful 介面均限於機房內網訪問,經由 API Gateway 的反向代理提供給外網。即前端在調用這些介面時,必須經過 API Gateway 調用。

認證授權

文章一開始提到的單體 Web 應用其實在 FreeWheel 有多套,分別對應於多個業務線或產品線。這些單體應用開發的階段有先有後,架構和實現的設計也存在著差別,其中很重要的一點就是認證方式的差別,為了滿足多個應用聯合登錄的需求,尤其是向後兼容 SPA 的聯合登錄,我們在後端以 Golang 開發了新的 SSO 服務。SPA 在登錄頁面調用 SSO 介面,登錄成功則獲取 token 並存入 cookie,這樣後續的介面請求就會將 cookie 傳入 API Gateway 以獲取認證信息。

至於授權(authorization),我們在現有的 Ruby On Rails 應用中大部分是基於 CanCan 框架實現的,改造為前後端分離架構後,我們將與導航、功能入口相關的授權信息從後端完整傳回前端,用前端代碼判斷特定導航或組件是否顯示、是否禁用。當然,RESTful 介面中仍有完整的授權判斷邏輯。如果有惡意用戶通過 hack 的方式修改了前端授權信息訪問了本不能訪問的界面,他依舊無法獲得列表數據、也無法提交數據修改。

後端 Docker 容器化

在業務模塊開發過程中,開發人員需要在開發前端代碼的同時能訪問到後端介面及測試數據。如果是單體應用的開發,開發人員只要配置一套開發環境即可達到這個目標,但在前後端分離後,前端開發人員除了配置前端開發環境,還要配置後端。後端代碼有更新時,需要及時檢出代碼並順利編譯,資料庫有更新時也需要執行相應的 SQL 腳本。這些日常工作成為前端開發人員的痛點。

後端 Docker 容器化有效解決了這一痛點。我們目前的 CI (Continuous Integration) Pipeline 會在後端代碼檢入遠程 Git 後觸發編譯,編譯成功後會創建一個包含該編譯版本的 Docker image 並上傳至公司內部的 Docker image 倉庫,類似的還有資料庫,以及其他中間件的 image。前端開發人員不再需要搭建後端開發環境,只需在開發機上安裝 Docker(如 Docker for Mac),在前端工程內會維護一個 docker-compose.xml,聲明了前端工程所需要的後端 Docker image,每次該文件更新後,前端開發人員只需要運行 docker-compose up -d 即可啟動一系列 Docker container,在本機運行完整的後端服務,這裡甚至包含了適用於開發的部分測試數據。

整合測試

前後端的分離和整合對質量保證提出了新的要求。我們在前端編寫 fetch 邏輯時,會以 mock 方式編寫對應的單元測試。後端每個介面也有響應的單元測試。而這兩端分別的單元測試還不足以保證軟體質量,理論上講,縱使兩者單元測試覆蓋率均達到 100%,也不能保證覆蓋所有用例。作為質量保證的關鍵環節,在兩端的單元測試都通過後,我們的 CI 會執行端到端的自動化測試。這些自動化測試模仿了用戶的使用場景,完整的覆蓋了前端、後端、資料庫乃至其他中間件。

漸進改造

SparkUI 的產生為前後端分離改造提供了堅實的基礎。如果按最理想的方式推進,只要業務開發團隊基於 SparkUI 對現有的 Ruby On Rails 的單體應用的前端部分、基於 Golang 微服務方式對其後端部分進行重構改寫、踐行前後端整合的最佳實踐,即可達成前後端分離的目標。而文章開頭曾提到,現存的 Rails 應用體積大、複雜度高,縱使有著業務開發團隊的全力支持,我們也很難在一個較短時間內徹底完成前後端分離的改造。更何況市場千變萬化,在業務部門服務老客戶、獲取新客戶過程中,產品經理們也會不斷地提出新的產品需求給我們的開發團隊,技術演進和業務推進兩者需要取得一個平衡。我們為達成這一平衡,所提出的方案是:漸進改造。

混合工程結構

我們的業務模塊在 Ruby On Rails 工程中是以 Module 方式存在的,除了公共的 MVC 和資源放在統一的 Module 里,每個業務 Module 都有自己的 MVC 和資源(這裡的資源特指 Javascript 和 CSS)。我們以業務 Module 作為改造的單元。

由於資源等限制,前後端分離改造在前端、後端的推進節奏並不一致。比較多的情況是 Module 前端改造先行,後端依舊沿用 Rails 原有的 Controller(也有部分適配工作)。在這種情況下,Module 經 SparkUI 改寫的前端(以下統稱為「新前端」)獨立於 Rails 工程之外進行打包部署所帶來的好處並不明顯,故將這部分新前端代碼的源碼依舊放在 Rails 工程 Module 目錄下,通過 Webpack 打包的 bundle JS/CSS 也按照 Module 對資源文件的約定(convention)放在 modules/my_module/app/assets/javascripts/my_module/compiled 目錄下,並藉由 Rails Asset Pipeline 打包進 Rails 工程發布包進行統一部署。

對於上述 bundle JS/CSS,我們仍使用 Rails 頁面模版作為入口,以期減少對 Rails 工程的影響:

至於路由,既然我們已經在新前端中實現前端路由,那在 Rails 端的後端(頁面)路由就可以委託給前端:

經由以上方案,我們在盡量短的周期改寫了更多的業務模塊,對運維的影響也非常小。對於這些業務模塊,我們預期在其改寫後端微服務時將前端代碼從 Rails 里徹底分離出來,完成該模塊的前後端分離。

在上述 Ruby On Rails 項目之外,FreeWheel 也啟動了若干個新項目。這些項目一步到位,直接按照前後端分離架構設計開發,其前端完全基於 SparkUI。我們也基於 Nginx 開發了一套輕量的靜態資源伺服器,前端利用 Webpack 編譯打包成 tar 包並獨立上線。

SparkUI 獨立工程

在小步快跑階段,我們將 SparkUI 源碼直接放在 Rails 公共 Module 中,令我們可以快速驗證可重用組件的設計是否滿足業務需要。然而這樣的結構會帶來幾方面問題:

版本管理。任何對 Spark 的迭代都會直接影響到業務模塊;

開發效率。SparkUI 是純 JS 庫,Rails 工程開發環境給 SparkUI 開發帶來一定負擔;

源碼許可權。任何業務模塊開發人員均可修改 SparkUI 代碼,帶來潛在代碼衝突;

跨工程復用。任何 Rails 工程之外的工程在利用 SparkUI 時都會比較繁瑣。

我們在 SparkUI 推出 1.0 版本時,將其源碼從 Rails 工程中摘出,移入一個新的純前端工程。SparkUI 在這個新工程中,仍由 Babel 和 Webpack 打包,但會作為 library 發布到公司 Nexus 上私有 NPM Repository 里。Rails 工程或其他純前端工程在其 package.json 和.npmrc 配置中聲明對特定版本 SparkUI 的依賴,執行 npm install 後則可以在前端代碼中使用 SparkUI。

這一改變大大解放了 SparkUI 和業務模塊兩方的生產力:

獨立的代碼庫可以隱藏部分 SparkUI 的內部 API 或工具代碼,防止業務模塊中濫用;

不同的發版節奏令 SparkUI 可以追逐更高的代碼質量,目前其源代碼共計 9.3 萬行,單元測試覆蓋率高達 99.43%;

業務模塊代碼可以更有計劃地升級 SparkUI 版本,在此之前無須反覆回歸測試。

新老 JS 代碼混用

對於 Rails 工程的部分功能模塊,其前端實現有很大一部分是基於 jQuery 開發的 JS。雖然這些代碼並不是基於 React 或 SparkUI 開發的,但它們也可以直接在前後端分離後的前端中獨立使用。我們在統一的粒度下,創建了一層對 React 友好的適配器 spark-adapter,對原有 jQuery JS 介面進行了封裝和隔離。業務模塊開發人員可以自行決定對於這一部分 JS 代碼是基於 SparkUI 重寫還是放在 Adapter 中以繼續沿用。

質量保證

作為商業應用,其軟體質量是絕不能妥協的。前後端分離改造也不能成為降低軟體質量的理由。我們保證質量的核心是測試:

SparkUI 組件庫本身要具有最高標準的單元測試覆蓋率;

業務模塊改寫為新前端時,也要基於 SparkUI 提供的基礎設施編寫單元測試;

對於 Rails 工程原有的自動化測試腳本,在業務模塊改造為基於 SparkUI 的新前端時,也要同時更新;

將測試加入 CI (Continuous Integration) Pipeline,一有 Merge Request 提交就執行測試,測試成功才允許 Merge;

各組 lead 在 Merge Request 上做代碼審查時嚴格把關。

另外一個有效實踐是為新上線新前端的模塊提供回滾機制。因為在這一階段,Rails 工程里特定功能模塊的新老前端代碼可以同時存在,只需在功能入口處設置一個開關,就可以在線上執行新前端遇到嚴重問題時隨時切換回老前端。

寫在最後

前後端分離架構是諸多前端應用系統的必經之路,而現實情況往往需要顧及諸多歷史架構。本文以單體應用為背景,設計開發可重用組件庫為手段,在保證效率與質量的基礎上,逐步改造為前後端分離架構。希望對同樣面對這一情況的讀者有所幫助。

文中提到的 SparkUI 框架,其中與 FreeWheel 業務並不直接相關的純技術部分,比如 spark-modula、spark-router 等包,我們已計劃將其逐步開源。希望屆時能與更多的前端技術專家和群體深入探討、共同進步,並最終對前端社區有所貢獻。

今日薦文


點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 InfoQ 的精彩文章:

騰訊遊戲容器雲平台的技術演進之路
1小時上手 TensorFlow 深度學習應用
智能泊車、環境控制……這些開發者作品會是未來「智慧社會」的藍本嗎?
變身智能運維,你應該這樣做

TAG:InfoQ |

您可能感興趣

Freehand Profit 將 Nike Air Mag 改造成鯊魚面罩
Supreme x The North Face 聯乘羽絨毯改造成 MA-1 Bomber 外套
拆解改造 Tom Sachs x Nike「Mars Yard Overshoe」,並深度解構這位紐約裝置藝術家
復古球鞋新改造!Nike Foamposite Vapor X 首度曝光
Sup x The North Face 聯乘羽絨毯還能改造成外套?
Places+Faces 攜手 GUESS 及 Dr Romanelli 打造多款改造單品
微軟OneDrive線上版即將獲得Fluent Design改造
拖鞋都能大改造,Nike Benassi Slides 「Fanny Pack」 曝光
SpringBoot:SpringDataRedis緩存改造
Fragment Design x Medicom Toy 九十周年 Mickey Mouse 經典改造 BERBRICK
安全扣系帶系統加入!White Mountaineering x adidas LXCON顏值大改造
clothsurgeon 全新足球主題改造單品即將上架
Levi『s x Air Jordan 4已成天價?這雙牛仔褲改造的Levi』s x Air Jordan 1更限量!
『Girlie』樂隊GFriend尋求形象改造
clothsurgeon 將 Louis Vuitton 圍巾改造成 Bomber Jacket
Oculus創始人Palmer Luckey對OculusGo進行改造,
Converse x Tyler,The Creator新配色大改造!最終版的小花花是你心儀的那一朵么?
我如何把 Windows「改造」成 macOS
舊衫大改造!Places + Faces x GUESS Jeans U.S.A. x DRx Romanelli 三方聯名!
來看看大神改造的「悟空」 OW x Nike Air Presto 2.0