怎麼評價淘寶 UED 的 Midway Framework 前後端分離? | 知乎問答精選

 

A-A+

怎麼評價淘寶 UED 的 Midway Framework 前後端分離?

2019年07月21日 知乎問答精選, 阿里巴巴 暫無評論 閱讀 2 ℃ 次

【賀師俊的回答(17票)】:

瀉藥。

1. 這系列文章寫得很好。

【注意,熟悉我的同志應該知道,我極少給出「很好」的評價。】

2. 這系列文章以及其背後的實踐重新樹立了淘寶系前端工程水準的領先地位。

【在此之前的一段時間內,至少從外部來看,淘寶已經落後於狼系和企鵝繫了。】

3. 這系列文章及其背後的實踐也證明了nodejs對於前端來說不僅在工具鏈而且在架構層面的意義。

【注意,這系列文章中的思路其實並不新鮮,但是在淘寶這樣規模而且業已非常成熟的產品中實施這樣的轉變,我認為是具有標誌意義的。】

4. 具體細節上仍有許多改善空間,如此系列的第4篇《前後端分離的思考與實踐(四)》在防禦XSS時還是存在一些傳統問題。這方面在參加杭JS的時候,我跟淘寶的herman同學有過溝通,具體就不在本問題展開了。因為這是局部問題,對整體架構影響不大。

5. 注意,以上評價的都是架構,或者說是思路。實施效果是不是好,我相信他們自己的說法。但Midway框架本身因為沒有看到具體文檔和代碼,而且其開發的目的首要是滿足淘寶的需求,因此其本身或其具體組件是否在普遍意義上適用和優秀,無法作出判斷。

【徐飛的回答(19票)】:

早上看到賀老出馬,也忍不住寫了一篇來談一下蘇寧這樣的公司對這方面的考慮。

近兩年來,我一直在思考如何改進前端體系的開發模式,這裡面最基礎的一點就是前後端的分離。談到前後端分離,也有一個誤區,認為僅僅是以瀏覽器作分界,把這兩部分的代碼分離出來。但其實是,做這件事情的本意,是要解決開發模式的問題,也就是要分離前後端開發人員的職責。

針對不同類型的Web產品,這個分離方式是有所不同的。對於Web應用,因為它跟服務端的交互基本就是AJAX或者WebSocket接口,所以這個分離是天然的,整個前端基本都是靜態HTML模板,JavaScript模塊,以及CSS和相關靜態資源,但是對於網購產品這樣的形態,它的做法就不一樣。

## 展示占主要部分的產品

網購產品的展示需求很重要,圖片等資源載入非常多,但相對的操作卻很少,基本只有搜索商品,加購物車,結算這樣的環節。傳統這樣的產品,多半是這麼個工作流程:

交互出高保真圖,前端去切圖,生成靜態HTML加展示效果,然後,注意,他不是自己接著往下做,而是交給另外一群開發人員,把它轉換成服務端模板,比如freemarker或者velocity之類,或者是smarty,為什麼要這麼做呢?因為這類產品講究一個首屏優化,是首屏而不是首頁,這就意味著對於首屏來說,經過的環節應當盡可能少,比如說,就不能先載入客戶端模板,再AJAX一個數據,然後去渲染一下。這麼做的性能肯定是不如服務端把HTML生成好,然後一次請求加載的。

這個過程肯定是有一些問題的,比如說,如果開發人員B在套模板的過程中,發現原先的靜態HTML部分有問題,應該怎麼辦?大家知道,一個對HTML和CSS都很熟悉,同時又可以寫業務邏輯的前端開發人員是很稀缺的,所以,多數情況下,這兩邊的技能是不同的,如果是簡單的頁面問題,這個開發人員可能自己也就解決了,如果他解決不了,怎麼辦?

如果B自己不改,把他已經搞成服務端模板的代碼返回給前端人員A,A也沒法下手,因為已經是服務端模板,A手裡沒有環境,改了之後不知道對不對,不能預覽。那麼,B把問題告訴A,A修改他的原始版本,然後再拿給B又怎樣呢?這時候B又麻煩了,他要對比兩次修改的部分,把自己前一陣的修改合併進去。

所以,不管怎麼搞,這裡面都很折騰。

Midway這個產品,他想要解決什麼問題呢?既然說前端人員沒法預覽模板的原因是,後端在使用服務端模板,那麼,我能不能找一種兩邊都可用的模板,你能在服務端渲染,我也能在客戶端預覽?服務端跟瀏覽器端同時都能運行的語言是什麼?只有JavaScript。

所以,大家就往nodejs裡面去發掘了,一個普通的JavaScript模板庫,它在瀏覽器端也可以渲染,在nodejs端也可以輸出成HTML,這時候,那些原來負責整合模板和邏輯的人員改用nodejs,是不是就解決這問題了?

想像一下這個場景多麼美好:前端來決定某個模板是服務端渲染還是客戶端渲染,當首屏的時候,就在nodejs裡面生成HTML,不是首屏的時候,就AJAX過來在瀏覽器端渲染展示。

從技術方案上看,這麼做很好了,工程上又帶來另外一些問題,那就是對熟練JavaScript開發人員的需求量大增。對阿里這樣的公司來說,前端有大幾百人,別的公司只能仰望,所以他當然可以放手一搞,但對我們蘇寧這樣,前端人數不大的,就麻煩了。如果我們也引入這樣的方案,就面臨把很大一部分Java開發人員轉化成JavaScript開發人員這麼一個問題,這個事情短期內肯定是無法解決的,所以反過來會增加前端這邊的壓力。所以暫時還用不了阿里這樣的方案,只能努力先提高人員水平再看情況。

服務端引入nodejs還有別的優勢,比如說請求合併等等,這個也可以用其他方式變通解決,比如加一個專門的跟現有後端同構的Web服務器,在那邊幹這些事。

## 展示和業務邏輯較均衡的產品

對於另外一些場景,也有類似的問題,比如支付產品,展示相對沒那麼重,但是又算不上Web應用,它面臨另外一種情況的前後端分離。這種場景下,前端的出靜態HTML和DOM操作類的JavaScript,業務開發人員負責寫後端,還有另外一部分業務邏輯的JS。

這裡的問題是什麼呢?是jQuery式代碼造成的協作問題。比如說:

$(".okBtn").click(function() { $.ajax(url, data) .success(function(result) { $("someArea").html(_.template("tpl", result)); });});

因為前端人員的稀缺,所以他不可能幫你把業務邏輯寫出來,所以說,這裡面$.ajax往裡的部分,要業務人員自己寫。然後,數據得到之後,又要去處理界面部分。

很多場景下,處理界面遠不是這麼搞個模板放上去就完事的,所以業務開發人員感到很煩悶,為了這麼一點小問題,反覆去找前端的人來搞,很麻煩,自己搞又特別花時間,所以都很苦悶。

這同樣是一種前後端的分離,只是這個分界線不在瀏覽器,而在於:是否寫業務邏輯。對付這種場景,解決辦法就是加強JavaScript代碼的規劃。現在流行那麼多在前端做MV*的框架,不考慮Angular這類太重量級的,來看看Backbone這樣的,它到底解決了什麼問題?

很多人說,Backbone雖然小,但根本不解決問題。這句話有一定道理,但前提條件是你自己的JavaScript代碼分層已經做得很好了。如果做得不好,它就可以協助你解決分層的問題。

剛才那段代碼,它的問題在哪裡呢,在於職責不清晰。一個函數只能做一件事,這是共識,但由於回調等方式,所以不經意就破壞了函數的單一性、完整性。我們試試來拆開它。

對於一個後端開發人員來說,他為什麼常常害怕寫前端代碼?是因為JavaScript語言嗎?其實不是,我們用來寫業務邏輯的時候,只會使用JavaScript一個很小的子集,對於這個子集來說,它並不存在多大的學習困難,最麻煩的地方在於DOM、BOM等東西,對於一個後端開發人員來說,如果要求他在掌握服務端代碼編寫的同時,還要去學這些,那真是有些不容易,所以,我們來給他省點事。

現在我們的出發點是,把這段代碼拆給兩個不同的人寫,一個人操作DOM,另外一個人只寫邏輯,絕對不操作DOM。前面這個代碼拆給前端維護,後面這個拆給業務開發人員。

最老土的方式:

a.js

$(".okBtn").click(function() { b1(data);});function a1(result) { $("someArea").html(_.template("tpl", result));}

b.js

function b1(data) { $.ajax(url, data) .success(a1);}

現在大家是不是相安無事了?

如果這麼做的話,AB雙方要做很多約定,也就是說,這個過程仍然是一個螺旋鏈。比如說,A先寫點擊事件的綁定,然後想起來這裡要調用一個請求,就去找B寫b1方法。B在寫b1的時候,又想到他要調用一個界面展示方法a1,然後又來找A寫,來回也挺折騰。

況且,有這麼一天,A在另外一個地方也想調用b1了,但是由於b1的回調已經寫死了,比較蠢的辦法就是在a1里面再判斷,這是什麼東西點擊造成的,然後分別調用不同的回調。如果情況複雜,那這個代碼寫出來真是沒法看。

如下:

a.js

var type = 0;$(".okBtn").click(function() { type = 1; b1(data);});$(".okBtn1").click(function() { type = 2; b1(data);});function a1(result) { if (type1) { $("someArea").html(_.template("tpl", result)); } else if (type2) { // ... } type = 0;}

b.js

function b1(data) { $.ajax(url, data) .success(a1);}

稍微好一些的辦法是,在b1中,直接返回這個請求的promise,這樣可以由調用方決定到底該幹什麼。

如下:

a.js

$(".okBtn").click(function() { b1(data).success(function(result) { $("someArea").html(_.template("tpl", result)); });});$(".okBtn1").click(function() { b1(data).success(function(result) { // ... });});

b.js

function b1(data) { return $.ajax(url, data);}

如果要對返回數據作統一處理,也可以很容易地在b1中,用promise重新封裝了返回出來,只不過這樣在a.js裡面,直接調用的就不是success,而是then了。

注意到這樣的代碼還有問題,比如說大量的全局函數,不模塊化,容易衝突。此外,沒有一個地方可以緩存一些共享數據,比如說這麼一個場景:

界面上兩個塊M和N,其中,M初始載入並加載數據,N在初始的時候不載入,而是在某個按鈕點擊的時候載入,而M和N中各有一個列表,數據來源於同一個服務端請求。

現在就有個問題,當N載入的時候,它的數據怎麼來?比較老土的方式,肯定是載入N的時候,同時也再去請求一下數據,然後渲染到N上。

從一個角度看,如果說不重新請求,N的這個數據應當從哪裡來?從另外一個角度看,如果重新請求了,發現數據跟之前的產生了變更,是否要同步給M,怎麼同步給它?

我們看看類似Backbone這樣的框架,它能提供怎樣的機制呢?或者如果我們不用它,怎麼自己把這個分層封裝得更好一些?

首先,是建立一個數據模型,在它上面添加數據的緩存:

define("model", [], function() { var Model = { data: null, queryData : function(param, fromCache) { var defer = q.defer(); if (fromCache || this.data) { defer.resolve(this.data); } else { var self = this; this.ajax(url, param).success(function(result){ self.data = result; defer.resolve(result); }); } return defer.promise; } }; return Model;});

這麼一來,我們在模型上作了數據的緩存,如果調用的時候加fromCache參數,就從緩存讀取,否則就請求新的。為了在兩種情況下,調用方接口能保持一致,把整個函數封裝成promise,以便接著調用。這裡的模型定義成單例了,假定是全局唯一的,可以根據需要調整成可實例化的。

這個時候,視圖層就要封裝DOM和事件的關聯關係:

define("view", ["model"], function(Model) { function View(element) { this.element = element; this.element.selector(".okBtn").click(function() { var self = this; var fromCache = true; Model.queryData({}, false).then(function(result) { self.renderData(result); }); }); } View.prototype = { renderData: function(data) { this.element.selector("someArea").html(_.template("tpl", result)); } };});

這個時候,多個視圖實例的情況下,數據也能夠較好地利用。

這樣,前端寫這個View,後端寫Model,可以作這麼個分工。

這個只是很簡陋的方式,在複雜場景下還有很多不足,在這裡先不展開了。更複雜的場景也就是類似Web應用那種方式,稍後專門寫一篇來展開。

## 小結

我們再來回顧前後端分離所要解決的問題,是分離前端和業務開發人員的職責,這個方案怎麼定,是應當隨著團隊狀況來確定的。比如阿里前端厲害,人多勢眾,他的前端就要往後推,去佔領中間層。我們蘇寧這樣的公司,前端比較薄弱,只能在很多場景下,讓出中間層,否則戰線鋪太廣只能處處被動。

同一個中途島,在不同的形勢下,占還是不佔,是很考驗前端架構師的一個問題。

對阿里的這種實踐,我們會持續圍觀,尋找並創造合適的出手時機。

【rank的回答(4票)】:

簡單說下自己的看法。

  1. 前端不再繼續「單純」在 kissy 上下功夫,而可以考慮向後的延伸架構是一種前端的進步,這種前端架構將重定義阿里的前端工程師工作,很多互聯網公司比阿里先行一步。

  2. 這個思路與與最早阿里很多前端沒有碰後端(例如模板)有很大的關係,用 NodeJS 作中間層能解決現面臨的問題,是一種不限於解決當前問題的長遠解決方案。

  3. 具體是否能解決和解決得好,在於細節,不在新,而在過渡。如,如何過渡目前 NodeJS 與原來的數據交互,如何灰度過渡,工作量等。

  4. 平台化與接口化思路(後端數據接口以 Services 存在)讓 amazon 收益非淺,現在後端平台化接口化在大公司趨勢明顯。
  5. 平台化需要更多更快的應用層開發選型,NodeJS 是不錯的一種。NodeJS 雖然還是有些問題,但從信息面與我們自己的應用經驗來看,已有慢慢成為後端 WebApplication 的一種很好的選型方案的趨勢。

總的來說,是個趨勢。

【Hex的回答(1票)】:

我認為這就是所謂的大前端開發模式。模式確實是好模式,但是真正實踐起來,和後端工程師的溝通和協調也會遇到很多問題。

我做過的幾個項目都是採用這種大前端的開發模式,前端基於Transformers框架+CodeIgniter組成大前端,這樣確實可以很好的隔離前後端,項目可維護性大大提高。

【鄧欣欣的回答(1票)】:

上周去杭州玩了下,和之前的阿里同事做了些技術交流,發現這一年,阿里的前端在流程改進上下了很大功夫; 題主所說的中途島應該是 UDC 團隊做的,應該說思路不是很新鮮,國外有 ebay 向 nodejs 的轉型案例,國內之前也有百度音樂移動端的案例;

但對阿里前端來說,意義確是很重大,解決了合作流程中的一個很大問題:之前阿里的前端是只寫靜態 demo,寫完給開發套模板,開發不太懂 html,漏寫個標籤,然後找前端調試,一來一去很折騰,是個必須干但又是個沒啥技術含量的事; 中途島可以很好的解決這類蛋疼事,但是請不要認為前端就因此會後端了,無非是之前瀏覽器用 ajax 請求接口,現在咱用 node http去請求唄,框架做得牛逼點,統一適配出前端的ajax 接口也不是不可能呀~~,想想嘛,為啥要用 node 呢? 牛逼直接寫 java 啊。。哈哈哈~

其他的 F2E 團隊也做些很不錯的流程改進工具,同樣不是很新鮮,但對阿里前端都是比較有意義的工具:

def: 項目構建與發佈工具,與阿里的 gitlab, scm 整合,各種 腳手架,build,combo,發佈,一條命令搞定,確實很方便;

dip:數據接口平台,定義業務線前後端數據格式的一個內部公共平台,基於 json-schema,好像也可以給你提供 mock 接口;

uitest:前端持續集成平台;之前這東西我是邊做邊吐槽的,似乎剛上線,類似 jenkis 這些,提交或者發佈代碼時,先幫你跑一次測試用例;目前通用測試庫比較少。

Trace:好像是叫這個名字吧,監控平台,這個比較早就有了,用來監控各個業務線頁面的運行狀況並搜集各種用戶數據,如分辨率,UA

我看來 def 和 dip 對阿里前端的作用會更大些,uitest 估計作用一般,阿里前端是不注重代碼質量的,測試用例也僅在幾個重要的直接影響交易的業務線會寫。

【許文敏的回答(0票)】:

確實不錯,從職責上來區分前後端分離才是王道,nodejs將成為前端工程師的基礎技能

【獵人豆豆的回答(0票)】:

不要把簡單的問題搞複雜,對於淘寶這樣規模的公司,有些牽一髮而動全身的改動,最好是在權衡風險和收益後再決定,我們是技術的使用者,而不要被技術牽著鼻子走.

【羅正燁的回答(2票)】:

前面前端的大牛們都說了,我換個角度聊聊。

這麼講吧,阿里的前端為什麼比其它公司走的遠,是因為他們有很多前端,還有很多不用寫大量業務邏輯的前端大牛。大牛的作用,就是折騰。阿里的前端工程師水平在自身領域實踐上已經跟得上後端。

但這個架構所謂的分離,其實是把很多原來前端不需要做的事攬到了自己的手上,增加前端架構師的KPI,讓前端做了更多的事,週報好寫。因為nodejs和前端都是js,所以學習成本並不算高,但是對一個技術人員的要求是比原來更高了。

但是,他們團隊有很多HC,有很多錢。。所以像我這種一個產品線只有一二個前端的,要是這麼玩兒,招人跟不上不說,然後還可以把自己累死。

所以技術選型和架構這種事,還是要根據自己團隊的能力和招人啊。

標籤:-前端開發 -前端工程師 -Node.js -網站開發


相關資源:





給我留言