告別是爲了彼此變得更好---SPA 先後端完全分離的探索之路

先後端分離,目前對於一個前端開發者來講,或者將其放在軟件開發領域的歷史背景下,都是再日常不過的一件事情了。17 年時進行了一個先後端完全分離方案的實踐,每次想起來這個方案時,總以爲它不夠完美。如今將其寫下來和你們交流,指望能夠促進方案的後續完善。html

邂逅

自從接觸前端開發,一共接觸到了三種前端項目發佈的方式:前端

  1. 經過 FileZilla 直接將前端資源文件上傳到業務服務器
  2. SSH 到業務服務器,經過 Git 更新前端項目代碼,而後進行新版本資源的構建
  3. 將構建好的前端資源文件推送到 cdn

剛加入丁香園前端團隊時,先後端分離的 SPA 項目的實現方式是:前端和服務端項目是兩個代碼倉庫,HTML 模板由服務端語言(JSP/PHP)輸出,HTML 引用的前端資源是經過前端資源發佈系統發佈到 cdn 上。涉及到 cdn,就會面臨 cdn 緩存的問題。解決 cdn 緩存問題的方案是傳統的時間戳機制。具體操做流程是服務端提供一個更新時間戳的接口,當前端每次發佈新版本後,去調用更新時間戳的接口。數據庫

基於時間戳機制的前端項目發佈流程大體爲:編程

  1. 在發佈系統進行項目發佈
  2. 經過相關接口更新時間戳

看上去這個流程很簡單高效,對不對?讓咱們來根據實際狀況來細化一下這個流程:後端

  1. 登陸發佈系統
  2. 在發佈系統點擊對應項目的發佈按鈕
  3. 等待前端資源構建併發布成功
  4. 資源發佈成功後,調用更新時間戳的接口
  5. 發佈完成

流程細化以後,咱們能夠把流程分爲5步,平均算下來每次前端同窗進行一次項目發佈,大概須要3分鐘。在等待發布系統構建資源的時候,前端同窗能夠並行的去作一些放鬆的事情,好比:去餐吧喝杯咖啡,上個廁所,隨手修個 bug。放鬆以後,更新一下時間戳,新版本發佈便大功告成。接下來即可以進入愉悅的新需求開發之旅了,一切都顯得如此愜意。瀏覽器

去年的我,也是這樣認爲的。由於當時在負責的新版調查問卷(SPA)和 Insight(能夠看作 n 個 SPA)等項目,大概平均天天發佈一次,因此用來發布的時間成本仍是能接受的。緩存

16年年末時,加入了大衆醫學部,開始和夥伴們一塊兒負責來問丁香醫生等項目。項目依舊是先後端分離的 SPA,構建好的前端資源依舊是發佈到 cdn,解決 cdn 緩存問題一樣採用的是時間戳機制。一切都是熟悉的配方,熟悉的味道。當參與了一段時間的需求迭代後,我發現事情彷佛沒有想象的那麼簡單。服務器

讓我有這種感覺的緣由,主要有兩點:微信

  1. 更新時間戳的接口即便在內網,也是須要鑑權的。並不像調查問卷等項目,直接在瀏覽器訪問一下更新時間戳的接口就能夠了。鑑權的方式是須要微信掃碼。
  2. 需求迭代速度謙虛一點說,很快。僅生產環境,幾乎天天都要發佈至少一次。

此時細化的一次發佈流程變爲:網絡

  1. 登陸發佈系統
  2. 在發佈系統點擊對應項目的發佈按鈕
  3. 等待前端資源構建併發布成功
  4. 資源發佈成功後,調用更新時間戳的接口(須要鑑權)
  5. 訪問更新時間戳的頁面,頁面彈出鑑權二維碼
  6. 發佈者拿起手機
  7. 打開微信,點擊掃一掃,掃描二維碼
  8. 微信接收到贊成受權的模板消息
  9. 點擊模板消息進行受權,受權成功後進入更新時間戳頁面
  10. 點擊更新時間戳按鈕
  11. 等待生效(服務端有緩存機制)
  12. 發佈完成

一次前端發佈的時間,平均在7分鐘左右。在實際工做中,須要在測試環境、(預發環境)、生產環境進行發佈。從團隊的角度來看,管理後臺等項目也在採用這種發佈方式。毛估一下,每週團隊在項目發佈上就須要花費 2 ~ 3 個小時((7min * 2 * 2 * 5)/ 60)。

此外,這種先後端分離的方式還有如下幾個問題:

  • HTML 模板開發效率較低。項目所使用的 HTML 文件須要前端同窗寫好後發給後端,後端再進行「套模板」,這種作法自己就有一個流程的複雜度。後端同窗若是對 HTML 掌握的不熟練,那麼須要前端同窗去跟後端同窗結對編程,來確保 HTML 的正確,此處有一個溝通成本。項目上線後,若是須要對 HTML 進行改動,須要前端先修改好 HTML,而後把 HTML 發給後端,後端再次進行「套模板」。後端同窗修改好 HTML 模板文件後,可能不會由於這一個改動進行發版,須要跟隨着後端項目的其餘改動代碼一塊兒發版,此時對於前端、測試同窗有一個等待的成本。
  • cdn 資源有一點點浪費。頁面中全部前端資源是使用同一個時間戳,這意味着每次更新時間戳都會更新頁面中的所有資源引用地址,從而當用戶再次使用應用時,須要從新下載頁面引用的全部資源。而前端的某個新版本,可能僅僅是須要更新部分資源文件便可。
  • 加長了應用的響應時間。這一點一樣是由上一點描述的時間戳機制致使的,從新下載「新」的資源而不是利用瀏覽器的緩存,必然會致使用戶須要等待更長的時間。

念念不忘,必有迴響

爲了解決上述問題,我設計了一個的方案:

方案說明:

  • 前端項目生產環境構建時,將文件名中加入 hash 值。
  • 前端資源發佈系統在資源構建成功後,將 index.html 同步到業務服務器(本着線上項目不作寫操做的原則:會把 HTML 文件放到後端項目和Node項目目錄以外的地方)。
  • 在業務服務器上新增一個 Node 服務。該服務的做用之一爲:收到瀏覽器端首頁的請求時,將 index.html 返回。
  • 當用戶訪問應用時,由運維將請求首頁 HTML 的請求轉給 Node 服務,其他業務接口保持原有方式不變。

方案肯定後,在團隊小夥伴的配合下,該方案在一個流量較小的項目上線了。

新的火花

上述方案存在一個問題的:在原有的技術體系中,引入了 Node.js。本質上是在穩定的技術體系下,增長了技術複雜度。所以,在不增長技術複雜度的前提下,須要開始探索新的解決方案。在後端同窗的配合下,新方案相比於引入 Node.js 方案改動以下:

  • 業務服務器給前端發佈系統提供同步 HTML 模板文件的接口,前端發佈系統每次成功構建前端資源後,調用該接口將模板文件同步給業務服務器。
  • 業務服務器獲取模板文件後,將文件內容存入數據庫,持久化存儲模板。在服務重啓和用戶請求時,服務端從數據庫中獲取模板。
  • 服務端能夠將從數據獲取的模板放到緩存中,這樣能夠避免高頻的讀取數據庫操做。對應的業務服務器須要監控數據庫中的數據變化狀況,以便於及時更新緩存中的模板資源。

方案上線後,前端項目的發佈流程變爲:

  1. 登陸發佈系統
  2. 在發佈系統點擊對應項目的發佈按鈕
  3. 等待前端資源構建併發布成功

前端項目發佈這件事,終於變成了點擊一下發布按鈕,整件事情就作好了。整個發佈流程耗時變爲不到一分鐘。此外,當前端須要改變 HTML 模板時,也再也不須要將文件發給後端同窗,苦苦等待後端項目的發版。

該方案上線後,組內同窗在週報中說起到的使用新方案後的感覺爲:

爲了方便前端同窗獲取同步模板的進展,在發佈系統中增長了同步過程的提醒:

沒有銀彈

上述方案相比於 JSP/PHP 提供 HTML 模板存在一個問題,就是在前端在構建 HTML 時,(暫時)不能將應用初始化的數據放入 HTML 中。解決方案是服務端提供一個接口,在應用啓動時去調用該接口獲取初始化數據。

對於前端加載優化,總體上思路上是須要減小網絡請求的,而此方案卻在增長網絡請求,這意味着頁面加載時間會變長。可是綜合利弊以後,仍是決定採用這個方案。

最終方案上線後,其實心中已經作好了頁面平均加載時間會變長的心理準備,可是有些意外的是,幾天後去 mta 看數據發現全國範圍內平均響應時間縮短了 0.2s 左右。爲何不升反降,目前我還不能得出一個準確的答案。猜想的一個緣由是: hash 值的方案避免了用戶進行沒必要要的資源更新。

待完善

上述方案雖然作到了前端項目的一鍵發佈,可是還不夠稱做爲一個完善的解決方案。由於該方案只是解決了 SPA 類型項目的發佈問題,對於以前「套模板」重 SEO 的項目而言,並非很適用。(提到 2018 年的技術浪潮下,如何開發一個重 SEO 的網站這個話題,又能夠寫一篇文章了,其中的心路歷程仍是蠻坎坷的)

言歸正傳,在前端資源發佈系統層面,該方案能夠考慮去增長文件歷史和發佈回滾功能,以備不時只需。

這個方案是和業務線的服務端同窗配合實現的,從公司層面來看,能夠考慮的點是是否能夠將這個方案作成一個通用的服務。

在和團隊的交流時,相學長提出能夠將 HTML 引用的資源抽象成 JSON Tree 進行存儲。以前看過一些相似的解決方案,不過目前本身仍是更傾向於分開的「更完全」,這樣可讓服務端同窗更安心的提供接口。

寫在最後

因爲水平有限,歡迎你們對此方案提出建議。很是期待。

本文做者:丁香園前端工程師 @志遙

相關文章
相關標籤/搜索