Ref: 先後端分離的思考與實踐(五篇軟文)javascript
其實就是在吹淘寶本身的Midway-ModelProxy架構。html
爲了提高開發效率,先後端分離的需求愈來愈被重視,前端
同一份數據接口,咱們能夠定製開發多個版本。java
(1) 後端 - 業務/數據接口,node
(2) 前端 - 展示/交互邏輯,nginx
探索一套基於 Node.js 的先後端分離方案,過程當中有一些不斷變化的認識以及思考,記錄在這裏。git
每一個人對先後端分離的理解不同,github
先後端分離的例子就是SPA(Single Page Application
),全部用到的展示數據都是後端經過異步接口(AJAX/JSONP
)的方式提供的,前端只管展示。編程
後端會幫咱們處理一些展示邏輯,這就意味着後端仍是涉足了 View 層的工做,不是真正的先後端分離。json
【後端不能關心界面的展現部分,因此spa分離的不徹底】
從職責上劃分才能知足目前咱們的使用場景:
(1) 前端:View 和 Controller 層。
(2) 後端:Model 層 (業務處理/數據等)。
玉伯 -《Web 研發模式演變》
頁面由 JSP、PHP 等工程師在服務端生成,瀏覽器負責展示。
Service 愈來愈多,調用關係變複雜。JSP 等代碼的可維護性愈來愈差。
但可維護性更可能是工程含義,有時候須要經過限制帶來自由,須要某種約定,使得即使是新手也不會寫出太糟糕的代碼。
如何讓先後端分工更合理高效,如何提升代碼的可維護性,在 Web 開發中很重要。
Web Server 層的架構升級,好比 Structs、Spring MVC 等,這是後端的 MVC 時代。
2004 年 Gmail 像風同樣的女子來到人間,很快 2005 年 Ajax 正式提出,加上 CDN 開始大量用於靜態資源存儲,
因而出現了 JavaScript 王者歸來的 SPA(Single Page Application
) 的時代。Ajax 帶來的 SPA 時代!
【開始在瀏覽器中重視起Javascript】
AJAX = Asynchronous JavaScript and XML(異步的 JavaScript 和 XML)。
AJAX 不是新的編程語言,而是一種使用現有標準的新方法。
AJAX 最大的優勢是在不從新加載整個頁面的狀況下,能夠與服務器交換數據並更新部分網頁內容。
AJAX 不須要任何瀏覽器插件,但須要用戶容許JavaScript在瀏覽器上執行。
這種模式下,先後端的分工很是清晰,先後端的關鍵協做點是 Ajax 接口。
看起來美妙,但實際上與 JSP 時代區別不大。
複雜度從 服務端的 JSP 裏移到了瀏覽器的 JavaScript,瀏覽器端變得很複雜。
相似 Spring MVC,這個時代開始出現瀏覽器端的分層架構:
SPA 讓前端看到了一絲綠色,但依舊是在荒漠中行走。
爲了下降前端開發複雜度,除了 Backbone,還有大量框架涌現,好比 EmberJS、KnockoutJS、AngularJS 等等。
這些框架總的原則是先按類型分層,好比 Templates、Controllers、Models,而後再在層內作切分,以下圖:
但依舊有不足之處:
一、代碼不能複用。
二、全異步,對 SEO 不利。每每還須要服務端作同步渲染的降級方案。
三、性能並不是最佳,特別是移動互聯網環境下。
四、SPA 不能知足全部需求,依舊存在大量多頁面應用。URL Design 須要後端配合,前端沒法徹底掌控。
隨着 Node.js 的興起,JavaScript 開始有能力運行在服務端。這意味着能夠有一種新的研發模式:
在這種研發模式下,先後端的職責很清晰。對前端來講,兩個 UI 層各司其職:
一、Front-end UI layer 處理瀏覽器層的展示邏輯。經過 CSS 渲染樣式,經過 JavaScript 添加交互功能,HTML 的生成也能夠放在這層,具體看應用場景。
二、Back-end UI layer 處理路由、模板、數據獲取、cookie 等。
經過路由,前端終於能夠自主把控 URL Design,這樣不管是單頁面應用仍是多頁面應用,前端均可以自由調控。後端也終於能夠擺脫對展示的強關注,轉而能夠專心於業務邏輯層的開發。
前一種模式的不足,經過這種模式幾乎都能完美解決掉:
經過 Node,Web Server 層也是 JavaScript 代碼,這意味着部分代碼可先後複用,須要 SEO 的場景能夠在服務端同步渲染,因爲異步請求太多致使的性能問題也能夠經過服務端來緩解。
SEO參考:前端後端分離,怎麼解決SEO優化的問題呢
基於 Node 的全棧模式,依舊面臨不少挑戰:
一、須要前端對服務端編程有更進一步的認識。好比 network/tcp、PE 等知識的掌握。
二、Node 層與 Java 層的高效通訊。Node 模式下,都在服務器端,RESTful HTTP 通訊未必高效,經過 SOAP 等方式通訊更高效。一切須要在驗證中前行。
三、對部署、運維層面的熟練了解,須要更多知識點和實操經驗。
四、大量歷史遺留問題如何過渡。這多是最大最大的阻力。
回顧歷史老是讓人感慨,展望將來則讓人興奮。上面講到的研發模式,除了最後一種還在探索期,其餘各類在各大公司都已有大量實踐。幾點小結:
一、模式沒有好壞高下之分,只有合不合適。
二、Ajax 給前端開發帶來了一次質的飛躍,Node 極可能是第二次。
三、SoC(關注度分離) 是一條偉大的原則。上面種種模式,都是讓先後端的職責更清晰,分工更合理高效。
四、還有個原則,讓合適的人作合適的事。好比 Web Server 層的 UI Layer 開發,前端是更合適的人選。
把node作爲UI渲染的「後端服務器"的使用方式,我以爲仍是頗有道理,很值得嘗試的。
之後端開發而言,我會更加看好go,但目前go在UI渲染方面仍是比較弱,把go挪去「真·後端」,UI渲染換成node,我以爲會是挺不錯的選擇。
提問者
回答者
現階段咱們主要之後端 MVC 的模式進行開發,這種模式嚴重阻礙了前端開發效率,也讓後端不能專一於業務開發。
解決方案是讓前端能控制 Controller
層,可是若是在現有技術體系下很難作到,由於不可能讓全部前端都學 Java,安裝後端的開發環境,寫 VM。
Node.js 就能很好的解決這個問題,咱們無需學習一門新的語言,就能作到之前開發幫咱們作的事情,一切都顯得那麼天然。
分層就涉及每層之間的通信,確定會有必定的性能損耗。可是合理的分層能讓職責清晰、也方便協做,會大大提升開發效率。分層帶來的損失,必定能在其餘方面的收益彌補回來。
另外,一旦決定分層,咱們能夠經過優化通信方式、通信協議,儘量把損耗降到最低。
舉個例子:
淘寶寶貝詳情頁靜態化以後,仍是有很多須要實時獲取的信息,好比物流、促銷等等,由於這些信息在不一樣業務系統中,因此須要前端發送 5,6 個異步請求來回填這些內容。
有了 Node.js 以後,前端能夠在 Node.js 中去代理這 5 個異步請求,還能很容易的作 Bigpipe,這塊的優化能讓整個渲染效率提高不少。
可能在 PC 上你以爲發 五、6 個異步請求也沒什麼,可是在無線端,在客戶手機上創建一個 HTTP 請求開銷很大,有了這個優化,性能一下提高好幾倍。
淘寶詳情基於 Node.js 的優化咱們正在進行中,上線以後我會分享一下優化的過程。
相對於只切頁面/作 demo,確定是增長了一點,可是當前模式下有聯調、溝通環節,這個過程很是花時間,也容易出 bug,還很難維護。因此,雖然工做量會增長一點,可是整體開發效率會提高不少。
另外,測試成本能夠節省不少。之前開發的接口都是針對表現層的,很難寫測試用例。若是作了先後端分離,甚至測試均可以分開,一撥人專門測試接口,一撥人專一測試 UI(這部分工做甚至能夠用工具代替)。
隨着 Node.js 大規模使用,系統/運維/安所有門的同窗也必定會加入到基礎建設中,他們會幫助咱們去完善各個環節可能出現的問題,保障系的穩定性。
咱們的初衷是作先後端分離,若是考慮這個問題就有點違背咱們的初衷了。即便用 Node.js 替代 Java,咱們也沒辦法保證不出現今天遇到的種種問題,好比職責不清。
咱們的目的是分層開發,專業的人,專一作專業的事。基於 Java 的基礎架構已經很是強大並且穩定,並且更適合作如今架構的事情。
能夠觀察到在這幾年,你們都傾向將 渲染這件事,從服務器端端移向了瀏覽器端。
而服務器端則專一於 服務化 ,提供數據接口。
View 這個層面的工做,通過了許屢次的變革:
(1) Form Submit 全頁刷新 => AJAX 局部刷新
(2) 服務端續染 + MVC => 客戶端渲染 + MVC
(3) 傳統換頁跳轉 => 單頁面應用
(1) 模版分離在不一樣的庫。有的模版放在服務端(JAVA),而有的放在瀏覽器端(JS)。先後端模版語言不相通。
(2) 須要等待全部模版與組件在瀏覽器端載入完成後才能開始渲染,沒法即開即看。
(3) 首次進入會有白屏等待渲染的時間,不利於用戶體驗
(4) 開發單頁面應用時,前端 Route 與服務器端 Route 不匹配,處理起來很麻煩。
(5) 重要內容都在前端組裝,不利於 SEO
不少人認定了 後端 = 服務端,前端 = 瀏覽器端 ,就像下面這張圖。
在中途島項目中,咱們把先後端分界的那條線,從瀏覽器端移回到了服務器端。
後端,專一於:(1) 服務層。 (2) 數據格式、數據穩定。 (3) 業務邏輯。
前端,專一於:(1) UI 層。 (2) 控制邏輯、渲染邏輯。 (3) 交互、用戶體驗。
用着同樣的模版語言 XTemplate,同樣的渲染引擎 JavaScript。
也由於有了 Node.js 這一層,能夠更細緻的控制路由。
一般咱們在瀏覽器端渲染一份模版時,流程不外乎是
在瀏覽器端載入模版引擎(XTmpleate、Juicer、handlerbar 等)
(2) 在瀏覽器端載入模版檔案,方法可能有
* 使用 <script type="js/tpl"> ... </script>
印在頁面上
* 使用模塊載入工具,載入模版檔案 (KISSY, requireJS, etc.)
* 其餘
(3) 取得數據,使用模版引擎產生 HTML
(4) 將 HTML 插入到指定位置。
從以上的流程能夠觀察到,要想要作到模版的跨端共享,重點其實在一致的模塊選型這件事。
市面上流行不少種模塊標準,例如 KMD、AMD、CommonJS,只要能將NodeJS的模版檔案透過一致模塊規範輸出到 Node.js 端,就能夠作基本的模版共享了。
《Midway-ModelProxy — 輕量級的接口配置建模框架》
Node.js 在整個環境中最重要的工做之一就是代理這些業務接口,以方便前端(Node.js 端和瀏覽器端)整合數據作頁面渲染。
如何作好代理工做,使得先後端開發分離以後,仍然能夠在流程上無縫銜接。
因而咱們但願有這樣一個框架,經過該框架提供的機制去描述工程項目中依賴的全部外部接口,對他們進行統一管理,同時提供靈活的接口建模及調用方式,而且提供便捷的線上環境和生產環境切換方法,使先後端開發無縫結合。
ModelProxy
就是知足這樣要求的輕量級框架,它是 Midway Framework 核心構件之一,也能夠單獨使用。
(1) 開發者首先須要將工程項目中全部依賴的後端接口描述,按照指定的 JSON 格式,寫入 interface.json
配置文件。
(2) 必要時,須要對每一個接口編寫一個規則文件,也即圖中 interface rules 部分。
該規則文件用於在開發階段mock數據或者在聯調階段使用 River 工具集去驗證接口。
規則文件的內容取決於採用哪種 mock 引擎(好比 mockjs, river-mock 等等)。
Ref: 你是如何構建 Web 前端 Mock Server 的?【這一部分算是單獨的一起知識點】
(3) 配置完成以後,便可在代碼中按照本身的需求建立本身的業務 Model。
例一
第一步 在工程目錄中建立接口配置文件 interface.json, 並在其中添加主搜接口 JSON 定義。
{ "title": "pad淘寶項目數據接口集合定義", "version": "1.0.0", "engine": "mockjs", "rulebase": "./interfaceRules/", "status": "online", "interfaces": [{ "name": "主搜索接口", "id": "Search.getItems", "urls": { "online": "http://s.m.taobao.com/client/search.do" } }] }
第二步 在代碼中建立並使用 Model。
// 引入模塊 var ModelProxy = require('modelproxy'); // 全局初始化引入接口配置文件 (注意:初始化工做有且只有一次) ModelProxy.init('./interface.json'); // 建立model 更多建立模式請參後文 var searchModel = new ModelProxy({ searchItems: 'Search.getItems' // 自定義方法名: 配置文件中的定義的接口ID }); // 使用model, 注意: 調用方法所須要的參數即爲實際接口所須要的參數。 searchModel.searchItems({q: 'iphone6'}) // !注意 必須調用 done 方法指定回調函數,來取得上面異步調用searchItems得到的數據! .done(function(data) { console.log(data); }) .error(function(err) { console.log(err); });
ModelProxy 的功能豐富性在於它支持各類形式的 profile 以建立須要業務 Model:
ModelProxy.create('Search.getItem');
ModelProxy.create({ getName: 'Session.getUserName', getMyCarts: 'Cart.getCarts' });
.
號後面的單詞做爲方法名// 下例中生成的方法調用名依次爲: Cart_getItem,getItem,suggest,getName ModelProxy.create(['Cart.getItem', 'Search.getItem', 'Search.suggest', 'Session.User.getName']);
ModelProxy.create('Search.*');
同時,使用這些 Model,你能夠很輕易地實現合併請求或者依賴請求,並作相關模板渲染。
例二
合併請求
var model = new ModelProxy('Search.*'); // 合併請求 (下面調用的 Model 方法除 done 以外,皆爲配置接口 Id 時指定) model.suggest({q: '女'}) .list({keyword: 'iphone6'}) .getNav({key: '流行服裝'}) .done(function(data1, data2, data3) { // 參數順序與方法調用順序一致 console.log(data1, data2, data3); });
例三
依賴請求
var model = new ModelProxy({ getUser: 'Session.getUser', getMyOrderList: 'Order.getOrder' }); // 先得到用戶id,而後再根據id號得到訂單列表 model.getUser({sid: 'fdkaldjfgsakls0322yf8'}) .done(function(data) { var uid = data.uid; // 二次數據請求依賴第一次取得的id號 this.getMyOrderList({id: uid}) .done(function(data) { console.log(data); }); });
例四
瀏覽器端使用 ModelProxy
此外 ModelProxy 不只在 Node.js 端可使用,也能夠在瀏覽器端使用。只須要在頁面中引入官方包提供的 modelproxy-client.js 便可。
<!-- 引入modelproxy模塊,該模塊自己是由KISSY封裝的標準模塊--> <script src="modelproxy-client.js"></script> <script type="text/javascript"> KISSY.use('modelproxy', function(S, ModelProxy) { // !配置基礎路徑,該路徑與第二步中配置的攔截路徑一致! // 且全局配置有且只有一次! ModelProxy.configBase('/model/'); // 建立model var searchModel = ModelProxy.create('Search.*'); searchModel .list({q: 'ihpone6'}) .list({q: '衝鋒衣'}) .suggest({q: 'i'}) .getNav({q: '滑板'}) .done(function(data1, data2, data3, data4) { console.log({ 'list_ihpone6': data1, 'list_衝鋒衣': data2, 'suggest_i': data3, 'getNav_滑板': data4 }); }); }); </script>
ModelProxy 以一種配置化的輕量級框架存在,提供友好的接口 model 組裝及使用方式,同時很好的解決先後端開發模式分離中的接口使用規範問題。
在整個項目開發過程當中,接口始終只須要定義描述一次,前端開發人員便可引用,同時使用 River 工具自動生成文檔,造成與後端開發人員的契約,並作相關自動化測試,極大地優化了整個軟件工程開發過程。
先後端分離模式下的安全解決方案
只負責瀏覽器環境中開發的前端同窗,須要涉獵到服務端層面,編寫服務端代碼。
而擺在面前的一個基礎性問題就是如何保障 Web 安全?
是什麼?
跨站腳本攻擊(XSS,Cross-site scripting),攻擊者能夠在網頁上發佈包含攻擊性代碼的數據,當瀏覽者看到此網頁時,特定的腳本就會以瀏覽者用戶的身份和權限來執行。
經過 XSS 能夠比較容易地修改用戶數據、竊取用戶信息以及形成其它類型的攻擊,例如:CSRF 攻擊。
怎麼辦?
預防 XSS 攻擊的基本方法是:確保任何被輸出到 HTML 頁面中的數據以 HTML 的方式進行轉義(HTML escape)。
Ref: HTML轉義字符大全
<textarea name="description"> </textarea><script>alert('hello')'</script> </textarea>
---------------------------------------------------------------------------
將 的值進行 HTML escape,轉義後的輸出代碼以下: --------------------------------------------------------------------------- <textarea name="description"> </textarea><script>alert("hello!")</script> </textarea>$description
/* 詳見原文 */
XSS 等注入性漏洞是全部漏洞中最容易被忽略,佔互聯網總攻擊的70%以上;
開發者編寫 Node.js 代碼時,要時刻提醒本身,永遠不要相信用戶的輸入。
基於先後端分離的多終端適配
基於 Web 的多終端適配進行得如火如荼,行業間也發展出依賴各類技術的解決方案。
1) 基於瀏覽器原生 CSS3 Media Query 的響應式設計、
2) 基於雲端智能重排的「雲適配」方案等。
本文則主要探討在先後端分離基礎下的多終端適配方案。
方案:由於 Node.js 層完全與數據抽離,同時無需關心大量的業務邏輯,因此十分適合在這一層進行多終端的適配工做。
進行多終端適配首先要解決的是 UA 探測問題。如今市面上已經有很是成熟的兼容大量設備的 User Agent 特徵庫和探測工具,這裏有 Mozilla 整理的一個Compatibility/UADetectionLibraries。
【值得一提的是,選用 UA 探測工具時必需要考慮特徵庫的可維護性,由於市面上新增的設備類型愈來愈多,每一個設備都會有獨立的 User Agent,因此該特徵庫必須提供良好的更新和維護策略,以適應不斷變化的設備】
其中,既有運行在瀏覽器端的,也有運行在服務端代碼層的,甚至有些工具提供了 nginx/Apache 的模塊,負責解析每一個請求的 UA 信息。
咱們把這個行爲再往前挪,掛在 nginx/Apache 上,它們負責:(1) 解析每一個請求的 UA 信息;(2) 再經過如 HTTP Header 的方式傳遞給業務代碼
這樣作有幾點好處:
(1) 咱們的代碼裏面無需再去關注 UA 如何解析,直接從上層取出解析後的信息便可。
(2) 若是在同一臺服務器上有多個應用,則可以共同使用同一個 nginx 解析後的 UA 信息,節省了不一樣應用間的解析損耗。a
【如下內容,往後再細看】
取得 UA 信息後,咱們就要考慮如何根據指定的 UA 進行終端適配了。
即便在 Node.js 層,雖然沒有了大部分的業務邏輯,但咱們依然把內部區分爲 Model / Controller / View 三個模型。
利用該圖,去解析一些已有的多終端適配方案。
優化後: