1. 程式人生 > >前後端分離實踐

前後端分離實踐

過多 rest css ken lan spec blog 什麽事 string

前後端分離並不是什麽新鮮事,到處都是前後端分離的實踐。然而一些歷史項目在從一體化 Web 設計轉向前後端分離的架構時,仍然不可避免的會遇到各種各樣的問題。由於層出不窮的問題,甚至會有團隊質疑,一體化好好的,為什麽要前後端分離?

說到底,並不是前後分離不好,只是可能不適合,或者說……設計思維還沒有轉變過來……

技術分享圖片

一體式 Web 架構示意

技術分享圖片

前後分離式 Web 架構示意

為什麽要前後端分離

比為什麽要前後端分離更現實的問題是什麽時候需要前後端分離,即前後端分離的應用場景。

說起這個問題,我想到了 2011 年左右,公司在以 .NET 開發團隊為主的基礎上擴展了 Java 團隊,兩個團隊雖然是在做不同的產品,但是仍然存在大量重復性的開發,比如用 ASP.NET WebPage 寫了組織機構相關的頁面,用 JSP 又要再寫一遍。在這種情況下,團隊就開始思考這樣一個方案:如果前端實現與後端技術無關,那頁面呈現的部分就可以共用,不同的後端技術只需要實現後端業務邏輯就好。

方案根本要解決的問題是把數據和頁面剝離開來。應對這種需求的技術是現成的,前端采用靜態網頁相關的技術,HTML + CSS + JavaScript,通過 AJAX 技術調用後端提供的業務接口。前後端協商好接口方式通過 HTTP 提供,統一使用 POST 謂詞。接口數據結構使用 XML 實現,前端 jQuery 解析 XML 很方便,後端對 XML 的處理工具就更多了……後來由於後端 JSON庫(比如 Newtonsoft JSON.NET、jackson、Gson 等)崛起,前端處理 JSON 也更容易(JSON.parse() 和 JSON.stringify()),就將數據結構換成了 JSON 實現。

這種架構從本質上來說就是 SOA(面向服務的架構)。當後端不提供頁面,只是純粹的通過 Web API 來提供數據和業務交互能力之後,Web 前端就成了純粹的客戶端角色,與 WinForm、移動終端應用屬於同樣的角色,可以把它們合在一起,統稱為前端。以前的一體化架構需要定制頁面來實現 Web 應用,同時又定義一套 WebService/WSDL 來對 WinForm 和移動終端提供服務。轉換為新的架構之後,可以統一使用 Web API 形式為所有類型的前端提供服務。至於某些類型的前端對這個 Web API 進行的 RPC 封裝,那又是另外一回事了。

通過這樣的架構改造,前後端實際就已經分離開了。拋開其它類型的前端不提,這裏只討論 Web 前端和後端。由於分離,Web 前端在開發的時候壓根不需要了解後端是用的什麽技術,只需要後端提供了什麽樣的接口可以用來做什麽事情就好,什麽 C#/ASP.NET、Java/JEE、數據庫……這些技術可以統統不去了解。而後端的 .NET 團隊和 Java 團隊也脫離了邏輯無關的美學思維,不需要面對美工精細的界面設計約束,也不需要在思考邏輯實現的同時還要去考慮頁面上怎麽布局的問題,只需要處理自己擅長的邏輯和數據就好。

前後端分離之後,兩端的開發人員都輕松不少,由於技術和業務都更專註,開發效率也提高了。分離帶來的好處漸漸體現出來:

1. 前後職責分離

前端傾向於呈現,著重處理用戶體驗相關的問題;後端則傾處於業務邏輯、數據處理和持久化等。在設計清晰的情況下,後端只需要以數據為中心對業務處理算法負責,並按約定為前端提供 API 接口;而前端使用這些接口對用戶體驗負責。

2. 前後技術分離

前端可以不用了解後端技術,也不關心後端具體用什麽技術來實現,只需要會 HTML/CSS/JavaScript 就能入手;而後端只需要關心後端開發技術,除了省去學習前端技術的麻煩,連 Web 框架的學習研究都只需要關註 Web API 就好,而不用去關註基於頁面視圖的 MVC 技術(並不是說不需要 MVC,Web API 的接口部分的數據結構呈現也是 View),不用考慮特別復雜的數據組織和呈現。

3. 前後分離帶來了用戶用戶體驗和業務處理解耦

前端可以根據用戶不同時期的體驗需求迅速改版,後端對此毫無壓力。同理,後端進行的業務邏輯升級,數據持久方案變更,只要不影響到接口,前端可以毫不知情。當然如果需求變更引起接口變化的時候,前後端又需要坐在一起同步信息了。

4. 前後分離,可以分別歸約兩端的設計

後端只提供 API 服務,不考慮頁面呈現的問題。實現 SOA 架構的 API 可以服務於各種前端,而不僅僅是 Web 前端,可以做到一套服務,各端使用;同時對於前端來說,不依賴後端技術的前端部分可以獨立部署,也可以應於 Hybrid 架構,嵌入各種“殼”(比如 Electron、Codorva 等),迅速實現多終端。

前後分離架構

任何技術方案都不是銀彈,前後分離不僅帶來好處,也帶來矛盾。我們在實踐初期,由於前端團隊力量相對薄弱,同時按照慣例,所有業務處理幾乎都是由後端(原來的技術骨幹)來設計和定義的,前端處理過程中常常發現接口定義不符合用戶操作流程,AJAX 異步請求過多等問題。畢竟後端思維和前端思維還是有所不同——前端思維傾向於用戶體驗,而後端思維則更傾向於業務的技術實現。

除此之外,前後分離在安全性上的要求也略有不同。由於前後分離本質上是一種 SOA 架構,所以在授權上也需要按 SOA 架構的方式來思考。Cookie/Session 的方式雖然可用,但並不是特別合適,相對來說,基於 Token 的認證則更適合一些。采用基於 Token 的認證就意味著後端的認證部分需要重寫……後端當然不想重寫,於是會將皮球踢給前端來讓前端想辦法實現基於 Cookie/Session 的認證……於是前端開始報怨(悲劇)……

誰來主導

這些矛盾的出現,歸根結底在於設計不夠清晰明確。毫無疑問,在開發過程中,主導者應該是架構師或者設計師。然而實際場景中,架構師或者設計師往往也是開發人員,所以他們的主要技術棧會極大的影響前後端在整個項目中的主次作用。這位骨幹處於哪端,開發的便捷性就會向哪端傾斜。這是一個不好的現象,但是我們不得不面對這樣的現狀,我相信很多不太大的團隊也面臨著類似的問題。

如果沒有良好的流程規範,通常前端接觸的到角色會比後端更多(多數應用型項目/產品,並非所有情況)。

  • 前端開發人員會受到項目/產品經理或客戶的直接影響:這個地方應該放個按鈕,那個操作應該這麽進行……;
  • 前端還要與美工對接——這樣的設計不好實現,是否可以改成那樣?客戶要求必須這麽操作,但是這個設計做不到;
  • 前端還要跟後端對接,對於某些應用,甚至是多個後端

換句話說,前端可以成為項目溝通的中心,所以比後端更合適承擔主導的角色。

接口設計

接口分後端服務實現和前端調用兩個部分,技術都是成熟技術,並不難,接口設計才是難點。前面提到前後端會產生一些矛盾。從前端的角度來看,重點關註的是用戶體驗,包括用戶在進行業務操作時的流動方向和相關處理;而從後端的角度來看,重點關註的是數據完整、有效、安全。矛盾在於雙方關註點不同,信息不對稱,還各有私心。解決這些矛盾的著眼點就是接口設計。

接口設計時,其粒度的大小往往代表了前後端工作量的大小(非絕對,這和整體架構有關)。接口粒度太小,前端要處理的事情就多,尤其是對各種異步處理就可能會感到應接不暇;粒度太大,就會出現高耦合,降低靈活性和擴展性,當然這種情況下後端的工作就輕松不了。業務層面的東西涉及到具體的產品,這裏不多做討論。這裏主要討論一點點技術層面的東西。

就形式上來說,Web API 可以定義成 REST,也可以是 RPC,只要前後端商議確定下來就行。更重要的是在輸入參數和輸出結果上,最好一開始就有相對固定的定義,這往往取決於前端架構或采用的 UI 框架。

常見請求參數的數據形式有如下一些:

  • 鍵值對,用於 URL 中的 QueryString 或者 POST 等方法的 Payload
  • XML/JSON/...,通常用於 POST 等方法的 Payload,也可以使用 multipart 傳遞
  • ROUTE,由後端路由解析 URL 取得,在 RESTful 中常用

而服務器響應的數據形式就五花八門各式各樣了,通常一個完整的響應至少需要包含狀態碼、消息、數據三個部分的內容,其中

  • 狀態碼,HTTP 狀態碼或響應數據中特定的狀態屬性
  • 消息,通常是放在響應內容中,作為數據的一部分
  • 數據,根據接口協議,可能是各種格式,當前最流行的是 JSON

我們在實踐中使用 JSON 形式,最初定義了這樣一種形式

{
    "code": "number",
    "message": "string",
    "data": "any"
}

code 主要用於指導前端進行一些特殊的操作,比如 0 表示 API 調用成功,非0 表示調用失敗,其中 1 表示需要登錄、2 表示未獲取授權……對於這個定義,前端拿到響應之後,就可以在應用框架層進行一些常規處理,比如當 code 為 1 的時候,彈出登錄窗口請用戶在當前頁面登錄,而當 code 為 2 的時候,則彈出消息提示並後附鏈接引導用戶獲取授權。

參閱:前後分離模型之封裝 Api 調用

一開始這樣做並沒有什麽問題,直到前端框架換用了 jQuery EasyUI。以 EasyUI 為例的好多 UI 庫都支持為組件配置數據 URL,它會自動通過 AJAX 來獲取數據,但對數據結構有要求。如果仍然采用之前設計的響應結構,就需要為組件定義數據過濾器(filter)來處理響應結果,這樣做寫 filter 以及為組件聲明 filter 的工作量也是不小的。為了減少這部分工作量我們決定改一改接口。

新的接口是一種可變結構,正常情況下返回 UI 需要的數據結構,出錯的情況則響應一個類型於原定結構的數據結構:

{
    "error": {
        "identity": "special identity string",
        "code": "number",
        "message": "string",
        "data": "any"
    }
}

對於新響應數據結構,前端框架只需要判斷一下是否存在 error 屬性,如果存在,檢查其 identity 屬性是否為指定的特殊值(比如某個特定的 GUID),然後再使用其 code 和 message 屬性處理錯誤。這個錯誤判斷過程略為復雜一些,但可以由前端應用框架統一處理。

如果使用 RESTful 風格的接口,部分狀態碼可以用 HTTP 狀態碼代替,比如 401 表示需要登錄,403 就可以表示沒有獲得授權,500 表示程序處理過程中發生錯誤。當然,雖然 HTTP 狀態碼與 RESTful 風格更配,但是非 RESTful 風格也可以使用 HTTP 狀態碼來代替 error.code。

用戶認證

認證方案很多,比如 Cookie/Session 在某些環境下仍然可行、也可以使用基於 Token 和 OAuth 或者 JWT,甚至是自己實現基於 Token 的認證方式。

基於 Cookie/Session 的認證方案

采用傳統的 Cookie/Session 認證方案並非不可行,只不過有一些限制。如果前端部分和後端部分同源,比如頁面發布在 http://domain.name/,而 Web API 發布在 http://domain.name/api/,這種情況下,原來的一體式 Web 方案所采用的 Cookie/Session 方案可以直接遷移過來,毫無壓力。但是如果前面發布和 API 發布不同源,這種方法處理起來就復雜了。

然後一般前後端分離的開發方式,不管是開發階段還是發布階段,不同源的可能性占絕大比例,所以認證方案通常會使用與 Cookie 無關的方案。

基於 OAuth 的認證方案

目前各大網站的開放式接口都是 SOA 架構,如果把這些開放式接口看作提供服務方(服務端),而把使用這些開放式接口的應用看作客戶端,那麽就可以產生這樣一種和前後分離對應的關系:

前端 ? 客戶端
     ?
   基於 OAuth 的認證)
     ? 
後端 ? 服務端

所以,開放式接口廣泛使用的 OAuth 方案用於前後分離是可行的,但在具體實施上卻並不是那麽容易。尤其是在安全性上,由於前端是完全暴露在外的,與 OAuth 通常實施的環境(後端?服務端)相比,要註意的是首次認證不是使用已註冊的 AppID 和 AppToken,而是使用用戶名和密碼。

基於 Token/JWT 的認證方案

雖然這個方案放在最後,但這個方案卻是目前前後端分離最適合的方案。基於 Token 的認證方案,各種討論由來已久,而 JWT 是相對較為成熟,也得到多數人認可的一種。從 jwt.io 上可以找到各種技術棧的 JWT 實現,應用起來也比較方便。

話雖如此,JWT 方案和以前使用的 Cookie/Session 在處理上還是有較大的差別,需要一定的學習成本。有人擔心 JWT 的數據量太大。這確實是一個問題,但是硬件並不貴,4G 也開始進入不限流量階段,一般應用中不用太在意這個問題。

前後分離的測試

前後分離之後,前端的測試將以用戶體驗測試和集成測試為主,而後端則主要是進行單元測試和 Web API 接口測試。與一體化的 Web 應用相比,多了一層接口測試,這一層測試可以完全自動化,一旦完成測試開發,就能在很大程度上控制住業務處理和數據錯誤。這樣一來,集成測試的工作量會相對單一也容易得多。

前端測試的工作相對來說減輕不了多少,前後分離之後的前端部分承擔了原來的集成測試工作。但是在假設 Web API 正確的情況下進行集成測試,工作量是可以減輕不少的,用例可以只關註前端體驗性的問題,比如呈現是否正確,跳轉是否正確,用戶的操作步驟是否符合要求以及提示信息是否準確等等。

對於用戶輸入有效性驗證這部分工作在項目時間緊迫的情況下甚至都可以完全拋給 Web API 去處理。不管是否前後端分離,Web 開發中都有一個共識:永遠不要相信前端!既然後端必須保證數據的安全性和有效性,那麽前端省略這一步驟並不會對後端造成什麽實質性的威脅,最多只是用戶體驗差一點。但是,如果前後端都要做數據有效性驗證,那一定要嚴格按照文檔來進行,不然很容易出現前後端數據驗證不一致的情況(這不是前後分離的問題,一體化架構同樣存在這個問題)。

小結

總的來說,前後分離所帶來的好處還是很明顯的。但是具體實施的時候需要一個全新的思考方式,而不是基於原有一體化 Web 開發方式來進行思考。前後分離的開放方式將開發人員從復雜的技術組合中解放出來,大家都可以更專註於自己擅長的領域來進行開發,但同時也對前後端團隊的溝通交流提出了更高的要求,前後端團隊必須要一同設計出相對穩定的 Web API 接口(這部分工作其實不管是否前後端分離都是少不了的,只是前後分離的架構對此要求更高,更明確地要求接口不只存在於人的記憶中,更要文檔化、持久化)。

原文鏈接:https://segmentfault.com/a/1190000012747428

前後端分離實踐