前後分離架構的探索之路

發表於2015-09-27

大約五年前,那時候我還是一個小小講師(蘋果 AATC 培訓認證),完全不懂程式設計為何物的菜鳥,一個偶然的機會讓我進入了公司的開發部門,任職什麼呢?使用者體驗設計師,原因很操蛋——我以前幹過廣告設計,做過餐飲服務行業,因而我有兩個優勢:能聆聽和揣摩客戶的需求,然後能做一些圖。

那時候很多像我們公司一樣的中小 IT 企業(200人左右,組成成分主要是大大小小的專案團隊)都有要做自主產品的訴求,這是市場決定的:出門找生意越來越難了。於是很多野路子出家的產品研發團隊就這樣誕生了……

說是產品研發團隊,其實都只是一群習慣了聽命於人去按照 RFP 實現功能的碼農罷了,和其他專案組相比唯一的差別大概就是“尚有夢想的鹹魚”而已。所以研發過程中的種種幼稚和操蛋,你用腳趾頭都能猜想得到。

一開始我就是把各位老大的設想整理成人人看得懂的需求,然後把它們串起來畫成草圖(mockup)再交給各種工程師去實現好了,這個角色類似於如今很時髦的“產品經理”。然而很快我發現大家老是加班,為什麼呢?調 CSS 樣式!

做慣了平面設計的我並不懂得把畫出來的東西變成瀏覽器裡的東西會有多麻煩。(今天,我在面試一些切圖頁面仔時,聽他們大談特談畫素級還原尚覺得好笑,但想想五年前的自己還是很有些羞愧的……然而更令我無語的是:到了如今初出茅廬的小前端們還把畫素級還原的切頁面當成是至高無上的本事,這件事情本身是不是很令人“沮喪”呢?)當寫頁面的同事一再告訴我我畫的東西不實際之後,我憋不住了——我就不信我畫的東西實現不出來!

抱著一口“怨氣”,我義無反顧的踏上了 HTML+CSS 這條路,其中過程不用多講,唯一的金玉良言只有一條:別看國內的教程,別信 w3school 之類的拼湊資源站。總之這事兒的結果是半年以後整個專案組幾乎所有的頁面都是我來寫了。(今天,已然成為前端架構師的我,所有頁面的自定義樣式還是得我親自寫,我不怪任何人因為我知道在很多工程師內心裡還是瞧不上寫 HTML+CSS 的技術的,你們不願意學我不勉強,我來。我還要感謝你們,為了能把飯喂到諸位的嘴裡,我花費大量的時間學習 CSS 框架的開發,從而精通了整個生態鏈,從 pre-processing 一直到 post-processing)。

這個世界就是這樣:一旦你專精了一項技能,你會很容易看出相關的技能在目前的水準如何。古人說:水漲船高。誠不欺我也。

所以成天耳濡目染 HTML+CSS 的我,經受著各種國外大神的視訊+教程耳提面命的我,很快就明白了一件事:我做的這個叫前端開發,HTML+CSS 只是起了一個頭,後面還有一座叫 JavaScript 的大山等著我。而我們做的前端開發還很嫩,活該你成天加班改樣式,修 bug,因為你一開始就沒走對路數。

幸好還不晚,不過這篇的主題是前後分離,所以我得按下快進按鈕直奔主題而去。

JavaScript 對我來講太難太難了,但是 jQuery 尚可,因為它有非常棒的 API 設計和相容性處理,很適合我這樣的菜鳥入門。那時候有一個叫 Jeffery Way 的傢伙錄製了一套 30 天學會 jQuery 的教程讓我受益匪淺,我認為他講得好,主要是兩個原因:

  1. 他不主要講各種 API 如何用,他從一開始就給我貫徹了一個重要的思想:學會看文件;
  2. 他主要講如何分析一個功能的實現,如何組織以 jQuery 為核心的程式碼邏輯;

在這裡先插一段旁述。各位能在工作中使用 Rails 的同行們,你們是無比幸運的!因為 Rails 已經把 View-Template 這一環節梳理的足夠簡單,哪怕完全不懂 Rails 的頁面仔,你稍微提點提點,他也能很快學會如何把靜態頁面套進 Rails 的模版裡去。

而我則是很不幸的,在那時我碰上了非常討厭的 JSP!你們千萬別笑,隨便去問那些寫頁面出身的前端們對 JSP 是什麼感受,絕對不會有好臉色的。對,我承認自己很菜。寫靜態頁面我行,但轉成 JSP 模版這件事在那時真的能把我難死!更要命的是,如果你把寫好的頁面交給後端工程師去套模版,最終的結果就是一塌糊塗!沒錯,他們根本不會細心周到的照顧你精心設計的每一個標籤,他們會做出各種各樣奇葩的事情來破壞原本完美的頁面結構,逼迫你不停的修改樣式和指令碼來適應這些“補丁”。

更要命的是除錯!原本寫 HTML+CSS 一個輕量級編輯器就搞定了,但等他們轉成 JSP 之後你再想去除錯就沒那麼簡單了。你需要:

  1. 執行環境,比如 Java+Tomcat,不懂吧?沒事,學!
  2. 生態鏈,比如 Maven 或 Gradel,不懂吧?沒事,學!
  3. IDE,比如 eclipse 或 IDEA Intellij,不懂吧?沒事,學!順便一提,知道沒接觸過 Java 的人想跑起一個應用來有多難嗎?我就是為此才愛上 Rails 的!
  4. ……

就這樣,為了除錯 HTML+CSS,你最終變成除了不會寫 Java 程式碼外其他全都會的 Java 開發工程師。

你們這些從後端出身的傢伙們能體會到前端頁面仔們邁出這一步需要多大的勇氣和毅力嗎?

你們能想象他們之所以不得不學做這些,就是因為你們無法認真對待 HTML+CSS+JavaScript 嗎?

為什麼要在後端的環境下做前端的事情?其實就為了三個字:擦屁股!

你可以說我們這一群人都很菜,我也承認,可是你要知道:環境不是時時處處都可以給你各種選擇的,有時候你唯一能做的選擇就是改變自己。那麼作為一個只懂 HTML+CSS+皮毛 JavaScript 的我,能做出什麼?不知從何時開始,“如果可以不再依賴任何環境就可以做好我們的份內之事就好了”這樣幼稚的念頭開始縈繞在我的腦袋裡……

回到 Jeffery 的視訊教程,在其中的一節他演示了 Ajax 獲取遠端資料然後動態修改 DOM 的例子,當時的例子裡用的是 Twitter 的 API,然後每隔一段時間拉取幾條新資料讓頁面即時重新整理這樣子……

不要笑,知道我當時有多震驚嗎?我覺得我們就他媽的是一群傻逼好嗎?

第二天我慌不擇路的把這段視訊拿給後端架構師看,問他實現這樣的東西,可行?他憋了半天:我們都是直接去資料渲染到 JSP 的,API 沒做過……

操!沒做過難道不能做?我趕緊丟擲了誘餌:如果搞的出來,以後你們再也不用套模版了!

這貨立馬答應了……

此後就是翻天覆地的折騰,我搜遍了所有能找到的資料,把它們翻成中文或者直接當面講給後端聽,有些東西我們都無法理解就先記下來,晚上回去我上 SO 問,上 Youtube 搜會議等資料看。

然後他們告訴我,如果換 Spring 的話可能會比較簡單,因為他們能百度到用 Spring 開發 API 的例子。於是我們就開始改造了。

改造的第一步是不用寫 JSP(或者少量的寫),但是靜態資源其實還是放在 Tomcat 容器裡的,因為我們經過嘗試發現跨域問題解決不了(是的,當時就是菜,連反向代理都不懂),不過沒關係,反正我已經學會了本地跑 Tomcat 了,至少我們可以不用寫 JSP 了嘛。現在回想一下當初搞前後分離的原始動機竟然就是為了不再去寫 JSP,多麼滑稽啊!然而反過來想想,這也映襯了一個事實:前端工程師們的生態環境是有多糟糕!

再然後就是把 jQuery 修煉到滿級開始無腦刷副本的無聊過程,當然在這個過程中也體驗了一些新東西,比如前端的模版引擎(Jade/Handlebars/Art等),模組系統(SeaJS/RequireJS)等等,JavaScript 的水平和理解有了長足的進步,終於開始有一個工程師的樣子了。

再之後就是大家都知道的劇本,node.js 橫空出世,一下子 HTTP Server,API Service,Shell Scripting……等等這些統統都可以用 JavaScript 來搞了,npm bower grunt gulp……等等這些應運而生,忽然間前端開始有了自己的生態系統!儘管它還很弱小還很混亂,但是它給了我們這些野路子出身摸爬滾打渾身泥水的傢伙們一道希望之光,它讓我們看到:

  1. 我們可以不依賴後端的執行環境:node.js
  2. 我們可以有自己的生態圈:npm
  3. 我們可以隨心所欲使用各種方便的開發工具:所以我後來成了 vim 黨
  4. ……

我們可以有很多可能,我們可以把我們擅長的事情做得更棒而不需要後端哥哥們操心,我們可以省去很多後端要 cover 的工作讓他們專心寫好自己的程式碼,我們設想中的分離是有搞頭的,不僅僅是為了分離而分離,而是為了更好的專精、多能、協作、管理而分離!

何以如此狹隘的看待前後分離?時至今日我也不懂為什麼有那麼多人抱持著種種懷疑與偏見。

你們不用擔心資料層邏輯會有冗餘,因為把 Model 的邏輯分攤到前端身上可以省去後端的部分程式碼和處理工作,而前端也可以更容易地按照業務來組合自己需要的 Model
你們不用擔心檢視層的快取,因為分離後前端只存在靜態資源,我們可以利用 CDN,利用 負載均衡,利用很多很多技術分攤過去必須讓後端來承擔的工作
你們不用擔心頁面渲染速度,首頁怕慢我們可以交給服務端來渲染,或者在中間加一個很簡單的 node server 來做首頁靜態化渲染,後面的事情交給前端就是,只快不慢
你們不用擔心要為多個客戶端做不同的資源排程,只要 API 規劃得到,一套 Service 可以支援多個客戶端的業務體系,而前端行有餘力甚至可以寫出多個版本來做 A/B 測試
你們不用擔心 ……

優點多了去了。

不是說這些優點目前都很成熟,也不是說實現它們沒有代價,但是你不去做就不可能成熟,代價也不是不可以有但關鍵是要看長遠的收益。

很現實的例子就是我們有一套系統本來是為自己做的,後來讓客戶知道了覺得很感興趣希望為自己定製一份。我們分析了一下,發現現有的 API 已經可以滿足使用者的需求,只需要針對幾個具體的業務邏輯再擴充幾個介面讓資料負載更合理便可,於是我們只用了三天就給客戶出了一個完全可用且相當穩定的 demo。客戶覺得滿意,開始按照他們的 VI 重新設計一套 UI,然後剩下的事就是找幾個頁面仔把頁面寫出來,現成的 Angular 邏輯往上一套小改幾處即可。

你會覺得不值得?

當然了,我在這裡不是鼓吹前後分離信仰,不是強求所有的事情都需要分離來做。一個產品的軌跡是需要產品研發團隊自己把控的,如果人云亦云流行什麼用什麼那也就和無腦兒沒啥區別了。有些場景也的確不需要分離,比如說入口網站,CMS,Mini Site 這類的需求就可以沿用成熟的開發體系。不過我之前談到過,探索和實踐分離體系還有一個重要的好處,就是能夠讓你現有的前端開發團隊摸索和整理出一套單兵作戰的環境體系,即便是不用分離架構,我單純用 node.js 寫一套入口網站,CMS,Mini Site 這樣的東西也不會比 Rails 慢啊!這樣一來,我還是可以把後端的資源用在更重要的底層服務或業務邏輯去,把那些和頁面 UI 互動相關,但又和資料層有著小小關聯的業務交給前端獨立完成,又有什麼不好呢?

前後分離=SPA?SPA=臃腫框架?

這一點我覺得有必要分析清楚,標題裡的兩個問號是我見到過最多的誤解。

首先,前後分離是架構上的事情,第一次做肯定很痛苦,但做一遍之後好處還是很多的。舉例項說明:

我們做過一個會議的應用,這個應用一開始設計是沒有 web 端的前臺的,只有一個管理後臺,前臺都是移動端。基於這個原因,我們還是分離的(因為你得提供 API 給移動端,不分離還能怎麼搞呢?),後臺用成熟的 Angular 很快就做好了。

沒想到後來有一個額外的要求,使用者要在建立會議的時候生成一套線上的會議手冊,這個會議手冊就是一個簡單的多頁面 CMS 系統,當使用者建立新會議的時候在後臺填寫手冊相關的內容,我們就要為它生成一系列的頁面來顯示(類似於 Mini Site),它有兩個特定要求:

  1. 不需要登入,公開訪問。然而最初的設計是沒有賬號就不能參加會議,需要報名,所以我們後臺和移動 App 都是直接先要求登入或註冊的,相應的 API 請求也是如此,有鑑權控制的。
  2. 要能多端訪問,還要能巢狀在原生應用的 webview 裡,因為加功能來不及了,只有一天時間。

傳統的架構你的寫頁面然後套模版去除錯,雖然只有不到10頁,但也是很費時間的。但我們已經分離了,現在為了這麼一個額外的需求也不值得再倒退回去,那麼怎麼做的呢?

  1. 單獨建一個會議手冊的專案;
  2. 模版用 Sass+Handlebars 很快搞定;
  3. 裡面的介面請求為每一個會議服務商繫結一個 token(後來還在後臺允許管理員重新生成和繫結 token),渲染頁面時寫死在 <meta> 標籤裡(就好像 CSRF 的處理),以此繞過鑑權
  4. 寫一個簡單的 node service,就幹一件事:渲染handlebars模版
  5. 建立會議的時候,Java API 傳會議 ID 給 node service,把渲染好後的頁面單獨儲存在靜態資源伺服器下(用 ID 建立獨立目錄),然後返回撥用地址
  6. 後臺收到會議地址,嵌入一個 iframe 做手冊預覽

一天搞定,完事。值得一提的是,整個手冊用了許多 HTML5 的新特性,比如 History API,SessionStorage,OfflineCache,GeoLocation,DesktopNotification,沒有用 Polyfill,因為這些都是可選特性,不支援就不作用,關鍵是:從頭到尾就是沒用 jQuery——不是我跟 jQuery 過不去,的確用不著,還嫌大。更不要提 SPA 框架了,完全沒有。

所以你看,分離架構可以讓我們很快完成這樣的小任務,並且可以單獨維護管理,也可以直接共享現有的 API 資源,它不一定只是為了 SPA 才分離,而且也沒有什麼技術難度。能用很短的時間完成還能保證質量,是因為我們有成熟的構建和CI,如果換成是當初 JSP 那一套,光配置個本地環境就夠夠的了,其他我都不敢想象。

分離是架構選擇,決定了你如何管理、分配與協調現有的資源,至於你分離後要做 SPA 還是其他模式的應用那完全是你的自由,並不是捆綁一加一的強制性決策。去構建一個分離體系當然會有挫折有代價,沒有人否認這個,然而一)這是可選的;二)你能否看到和利用它的好處。

至於 SPA 一定是臃腫的嗎?保持這種思想的我只能說你目光所見過淺。相比十年前的 web 開發,我能說現在 Rails 很臃腫嗎?別說十年前了,就是今天一樣也有人說 Rails too heavy!你覺得呢?那又怎樣呢?還不是該用就用?水平高的自然知道拆分和減肥,連 Rails 自己都知道瘦身一個 Rails API 出來,你以為所謂“臃腫”是 SPA 框架的專利嗎?SPA 之所以臃腫是有兩個主要的現階段環境因素決定的:

  1. 非常多的新特性層出不窮,為我們開發更豐富強大的應用程式提供了武器和彈藥。但是瀏覽器(及其他執行環境)和裝置碎片化的問題導致這些新特性無法提供始終一致的表現或效能,於是各種框架就要在底層做相容性的補充與改良,順便還要為尚未形成標準的新特性重新封裝 API 介面。比如說 Ember 幹嘛要造一個 Object 介面出來?不就是因為 Observable 介面沒有嗎?有什麼大不了的?ES2016就有了(非常可能),或者你可以不用 Ember 自己的,用第三方的 Observable 元件來代替也行。
    jQuery 做的事情和這有多大區別?沒錯,jQuery 是相對輕了,可是它負責的面兒也少啊,哪位用 jQuery 的不都得附帶十個八個外掛的?合在一起就輕了?
  2. 相對的,前端工程這塊業界整體的水平差距很大,牛的特牛,菜的特菜;但是菜的也希望用牛的工具,可又沒那個底蘊解決牛的能解決的問題,於是牛的就把一個一個特性統統封裝好聯絡在一起,讓你儘可能快速簡單的就能用到。
    如果大部分的工程師都成長起來了,也就沒有必要非得搞大而全的方案了,React 及其生態體系不就是一個很好的例子嗎?不給你搞大而全,只給你搞小而專,你以為你把那一堆連起來用就不叫 SPA 了?幼稚!

再說一遍,SPA 是一種產品的技術形態,而不是特定某(幾)種框架下的產物,滿足這種技術形態的工具鏈可以臃腫也可以簡潔,這是因為環境和人決定的。

Single Page Application,not Some Particular Application

前後分離還有一方面的作用。前端工程師都有一個普遍的特點:你讓他們寫個頁面信手拈來,但是你讓他們負責一個完整的業務多半就得抓瞎。為什麼?因為他們太偏門。最近一兩年我開始大量的面試和儲備新人,十有八九都是這樣的:HTTP?不懂!Ajax?懂!(你覺得合理嗎?)jQuery 請求 API?會!Promise 用過?……沒。換個說法,deferred 物件?哦哦,見到過!(你覺得合理嗎?)

諸如此類的問題屢見不鮮,讓我對前端這個行業的未來充滿憂慮。當初我也是從一竅不通的菜鳥開始,若那時沒有“一定要擺脫 JSP”的幼稚理想,我怎麼可能通過摸索前後分離讓自己擁有今天這樣相對全面的見識和理解?我走過的路讓我明白,探索前後分離並不是像很多旁觀者說的“為了分離而分離”,反而是“為了更好的理解 web 開發這回事而分離”。

因為當你開始摸索這條路,你就不得不面對許多根本性的問題,拿跨域資源共享來說吧,以前的架構前端工程師是極少需要面對這種問題的,但你只要一分離就必然會碰到,然後你就要去學諸如 JSONP,CORS,HTTP 協議,瀏覽器安全機制,PreFlight Request,反向代理等等技術細節。看似加重了學習成本(要我說,這些原本應該是學校的責任!),但作為同事,你希望你身邊做的是個只會“追求畫素級還原”的頁面仔呢?還是對上述知識點有著紮實的理解和實踐經驗的工程師呢?

說到這,就昨天有人在 SF 上問了個問題,大致是問:JavaScript 怎樣才算學好了?總覺得需要自己能寫一個庫或框架出來才算學好了,大家怎麼看?

我剛好和人吵完了架,靜下心想了想之後作出瞭如下回答:

這個寓言想表達的意思是不言而寓的,我很贊同這裡一位朋友說的:我們不應該有前端後端之分,我們可以有專精之處,但是對於 web 開發這回事該懂的都應該要懂,否則你怎麼可能打得贏?同理,如果說後端工程師需要靠寫頁面來了解前端的話,那麼前端也應該有類似的方式來了解後端做的一些事情。在這裡探索前後分離就是一個很好的教學與實踐相結合的手段。沒有哪個頁面仔會甘於永遠切圖寫頁面,他們也很羨慕後端哥哥們大神般的風騷,只是他們所處的環境造成了他們只知道數十年如一日的就懂切頁面了,如果能多給他們一些提攜與幫助,誰敢說他們以後不會成為江湖高手?

很多人拿工作忙,缺人手,創業公司求效率等藉口來回避在技術道路上的探索和進取,說真的我個人非常非常可以理解,我當初所做的事情其實和創業什麼的也沒多大區別,我們人手也很緊缺——今天我們只有三個人維護著四款前後分離架構的中大規模產品,這些產品有 Saas 版本的,還有大大小小十幾個在客戶那裡獨立部署的,你沒看錯就三個人!一個 Java 工程師,一個懂 Java 的前端工程師,再加上我這個什麼都懂一點但什麼都不專精的萬金油。

我們做的還不夠好,但我們已盡力做到自己能做的最好,與我們這五年來碰到的風風雨雨相比較,探索前後分離這真的不算個大事兒好嗎?

作為前端工程師(並且是懂得和尊重後端開發的),我很欣慰能活躍在這個時代,就像有人說的:這是前端最好的時代,也是前端最壞的時代。然而歷史無數次證明:真金不怕火煉,英雄應運而生。那些後端語言環境和框架體系難道沒有經歷過同樣的革新與變遷?就因為我們過去是隻會寫 jQuery 的頁面仔,所以我們就應該永遠這樣停滯不前?

這就是我探索前後分離的過程和心得感想,主要是在離職前為過去五年做一個總結。寫得比較凌亂也沒什麼技術含量,根本的意思還是要鼓勵眾多的前端同行們:學校沒有我們的專業課,社會對我們的工作沒有準確的認知和評價,這都不要緊!重要的是我們自己不能看輕自己的能力,不能放棄自己的價值。在學習和工作尚有餘力的時候勇於探索吧,別管別人說什麼,本事學到手才是最重要的,要記住:你是一個工程師,你不是一個頁面仔!

相關文章