這篇是我參加QCon北京2014的演講內(nèi)容: 提綱:企業(yè)應(yīng)用在軟件行業(yè)中占有很大的比重,而這類軟件多數(shù)現(xiàn)在也都采用B/S的模式開發(fā),在這個日新月異的時代,它們的前端開發(fā)技術(shù)找到了什么改進點呢? B/S企業(yè)軟件前端開發(fā)模式大體上與桌面軟件類似,都是偏重量級的,在前端可能會有較多的業(yè)務(wù)邏輯,這些業(yè)務(wù)邏輯如何被合理模塊化,與界面分離,以便測試,成為這個領(lǐng)域的一個重要挑戰(zhàn)。另一方面,由于企業(yè)應(yīng)用的界面相對規(guī)整,偏重的是數(shù)據(jù)存取,沒有太多花哨的東西,所以常見的界面控件也是可枚舉的,如何讓開發(fā)界面的工作能更快完成,甚至由不擅長編寫代碼的業(yè)務(wù)設(shè)計人員來做,與界面原型的工作合二為一,能提高不少開發(fā)效率。 在AngularJS等MV*框架出現(xiàn)之后,給這個領(lǐng)域帶來一些契機,架構(gòu)師們能夠有機會去重新規(guī)劃前端的架構(gòu),甚至是開發(fā)流程,從而讓整個軟件的生產(chǎn)更為高效。 本文將探討它給這個領(lǐng)域帶來的變化。 正文:企業(yè)應(yīng)用前端的特點企業(yè)應(yīng)用系統(tǒng)是一種很常見的軟件系統(tǒng),這類系統(tǒng)的特點是面向某個行業(yè),功能較復(fù)雜,對界面的要求一般是整齊,不追求花哨。這類系統(tǒng)通常有C/S和B/S兩個流派,其中的B/S方式因為部署和集成的便利,使用得較為普遍。 同樣是在瀏覽器中做東西,寫企業(yè)應(yīng)用和網(wǎng)站的差別也很明顯。企業(yè)應(yīng)用的業(yè)務(wù)邏輯較重,前端有一定的厚重性,但是對效果并不追求很多,主要是各類控件的使用,表單的存取值等等。 企業(yè)應(yīng)用產(chǎn)品的一些特點如下:
企業(yè)應(yīng)用常見的前端框架在開發(fā)B/S企業(yè)應(yīng)用前端的人群中,有很大一部分群體選擇了服務(wù)端的組件化方式,比如JSF之類,它的弊端是與異構(gòu)服務(wù)端的第三方系統(tǒng)集成比較麻煩。也有不少人使用Bindows和ExtJS這樣的框架,最近的KendoUI也是個不錯的選擇。 每種類型選一個有代表性的來說說:
曾經(jīng)的企業(yè)B/S應(yīng)用幾件寶有一段時間,我們幾乎只有IE6,所以那個時候的前端開發(fā)人員很快樂,沒有兼容的壓力。那時候,我們?nèi)绾螛?gòu)建前端應(yīng)用呢?參見這里的分享。
具體實例請參考用這些技術(shù)構(gòu)建的一個典型企業(yè)應(yīng)用。 單頁應(yīng)用和前端分層當(dāng)時這些系統(tǒng)的構(gòu)建方式也可以算單頁應(yīng)用,我們用iframe來集成菜單,每個菜單有自己獨立的功能,整個主界面是始終不會刷新的。 時光飛逝,這些年,前端有了什么本質(zhì)的改變,產(chǎn)生了翻天覆地的變化嗎?有時候我們回顧一下,卻發(fā)現(xiàn)多數(shù)都是在增加完善一些細節(jié),真正有顛覆性的有比如以RequireJS和SeaJS為代表的模塊定義和加載庫,npm這樣的包管理器,grunt,gulp,百度fis這樣的集成開發(fā)模式。為什么它們算是本質(zhì)改進呢?因為這些標(biāo)志著前端開發(fā)從粗放的模式,逐漸變化到精確控制的形態(tài)。比如我們再也不能不管代碼的依賴關(guān)系,也不能一打開界面就不分青紅皂白把所有可能要用到的代碼都立刻加載過來,那個時代已經(jīng)過去了,從任何角度講,現(xiàn)代的前端開發(fā)都在精細化,從代碼的可控,到界面體驗的精細優(yōu)化,到整個團隊甚至公司甚至互聯(lián)網(wǎng)上的組件共享,以及前端團隊協(xié)作流程的改進,這已經(jīng)是一個很成規(guī)模的產(chǎn)業(yè)了。 我們把眼光放到2013年,在這一年里最火的前端技術(shù)莫過于NodeJS和AngularJS,前者給我們帶來的是一種開發(fā)方式的改變,后者是一種典型的前端分層方案。Angular是前端MV*框架的一個流派,用過的人都會覺得很爽。它爽在什么地方呢?因為它幫我們做的事情太多了,一個雙向綁定,無所不包,凡是存取值相關(guān)的操作,基本都不用自己寫代碼。在企業(yè)應(yīng)用前端功能里,表單的存取值和校驗占據(jù)了很大的比例,這些事都不用干了,那簡直太好了。如果就因為這個用Angular,那還有些早。有一些第三方代碼被稱為庫,另外一些稱為框架,Angular是框架而不是庫??蚣艿暮x是,有更強的約束性,并非作為輔助功能來提供的。 先看一下企業(yè)應(yīng)用的通常形態(tài)吧,會有一個可配置的菜單,然后多半會采用MDI的形式,能打開多個業(yè)務(wù)功能,用選項卡的形式展示起來,可以隨時切換操作。每個人每天常用的功能是可以窮舉的,他進入系統(tǒng)之后,一般要用到下班才關(guān)掉。所以這種系統(tǒng)非常適合做成單頁應(yīng)用,開始的時候加載一個總體框架,每點擊一個菜單,就加載這個菜單對應(yīng)的功能模塊,放在一個新的選項卡或者別的什么地方展示出來。 在早期做這種系統(tǒng)的時候,一般都會用iframe來集成菜單,這種方式很方便,但是每個菜單頁都要載入共同的框架文件,初始化一個環(huán)境,數(shù)據(jù)之間也不能精確共用。所以現(xiàn)在我們做企業(yè)信息系統(tǒng),不再適合用iframe來集成菜單,所有菜單的業(yè)務(wù)代碼,會在同一個頁面的作用域中共存。這在某些方面是便利,比如數(shù)據(jù)的共享,一個選擇全國城市的下拉框,在多個功能中都存在,意味著這些城市的數(shù)據(jù)我們可以只加載一次。但從另外一個角度來說,也是一種挑戰(zhàn),因為數(shù)據(jù)之間產(chǎn)生干擾的可能性大大增加了。 我們回顧一下在傳統(tǒng)的客戶端開發(fā)中是怎么做的,早在經(jīng)典的《設(shè)計模式》一書中,就提到了MVC模式,這是一種典型的分層模式。長期以來,在Web開發(fā)人員心中的MVC,指的都是Struts框架的那張圖,但我們單頁應(yīng)用中的MVC,其實更接近最原始的《設(shè)計模式》書中概念。所以我們要在前端分層,而不僅僅把整個前端都推到視圖層。 做單頁應(yīng)用,前端不分層是很難辦的,當(dāng)規(guī)模擴大的時候,很難處理其中一些隱患。分層更重要的好處是能夠從全盤考慮一些東西,比如說數(shù)據(jù)的共享。跨模塊的數(shù)據(jù)共享是一個比較復(fù)雜的話題,搞得不好就會導(dǎo)致不一致的情況,如果考慮到在分層的情況下,把各種數(shù)據(jù)來源都統(tǒng)一維護,就好辦多了。 所以,以AngularJS為代表的前端MV*框架最重要的工作就是做了這些對于分層的指導(dǎo)和約束性工作,在此基礎(chǔ)上,我們可以進一步優(yōu)化單頁應(yīng)用這類產(chǎn)品。 前端的自定義標(biāo)簽體系構(gòu)建一個大型企業(yè)應(yīng)用,最重要的是建立整套組件體系。一般針對某行業(yè)的軟件,長期下來都會有很多固定的模式,可以提煉成組件和規(guī)則,從前端來看,體現(xiàn)為控件庫和前端邏輯??丶爝@個是老生常談,在很多框架里都有這個概念,但各自對應(yīng)的機制是不同的。 從寫一個界面的角度來講,最為便利的方式是基于標(biāo)簽的聲明式代碼,比如我們常見的HTML,還有微軟的XAML,F(xiàn)lex中的MXML等,都很直接,設(shè)想一下在沒有可視化IDE的情況用類似Java Swing和微軟WinForm這樣的方式編寫界面,毫無疑問寫XML的方式更易被接受。所以,我們可以得出初步的結(jié)論,界面的部分應(yīng)該寫標(biāo)簽。 很遺憾,HTML自帶的標(biāo)簽是不足的,它有基本表單輸入控件,但是缺乏DataGrid,Tree之類更富有表現(xiàn)性的控件。所以絕大多數(shù)界面庫,都采用某種使用JavaScript的方式來編寫這類控件,比如: <div id="tabs"> <ul> <li><a href="#tabs-1">Nunc tincidunt</a></li> <li><a href="#tabs-2">Proin dolor</a></li> <li><a href="#tabs-3">Aenean lacinia</a></li> </ul> <div id="tabs-1"> </div> <div id="tabs-2"> </div> <div id="tabs-3"> </div> </div> $(function() { $( "#tabs" ).tabs(); }); 如果這樣,這些復(fù)雜控件就都要通過JavaScript來創(chuàng)建和渲染了,這與我們剛才提到的原則是違背的。那我們尋找的是什么呢,是一種能擴展已有HTML體系的東西。在早期,IE瀏覽器中有HTC,可以通過引入命名空間來聲明組件,現(xiàn)在的標(biāo)準瀏覽器中又引入了Web Components,在Polymer這個框架中可以看到更多的細節(jié)。說到底,這類方式要做些什么事情呢?
從另外一個角度講,為什么我們非要這么做不可?最大好處來自哪里?對于大型項目而言,管理成本和變更成本都是需要認真考慮的。如果一個組件,需要在DOM中聲明一個節(jié)點, 然后再用一個js去獲取DOM,把DOM渲染出來,再填充數(shù)據(jù)的話,這個過程的管理成本是很大的,因為HTML和JS這兩個部分丟了一個都會有問題,無論在什么時候,維護一個文件總是比維護多個文件要強的,我們看HTC那種方式,為什么它的使用成本很低,因為它可以把控件自身的DOM、邏輯、樣式全部寫在自己內(nèi)部,整個一個文件被人引用就可以了。在現(xiàn)在這個階段不存在這么好用的技術(shù)了,只能退而求其次。 所以,在這個點上,Angular帶來的好處是可擴展的標(biāo)簽體系,這也就是標(biāo)簽的語義化。Angular的主打功能之一是指令,使用這種方式,可以很容易擴展標(biāo)簽或者屬性。比如,業(yè)務(wù)開發(fā)人員可以直接寫: <panel> <tree data="{{data}}"></tree> </panel> 這樣多么直觀,而且可以跟原有的HTML代碼一起編寫,不造成任何負擔(dān)。語義化的標(biāo)簽是快速編寫界面的不二法門。 業(yè)務(wù)邏輯有了語義化標(biāo)簽之后,如果我們只寫界面不寫邏輯,那也夠了,但現(xiàn)實往往沒有這么美好,我們還要來考慮一下業(yè)務(wù)邏輯怎么辦。 企業(yè)應(yīng)用一般都是面向某行業(yè)的,在這個行業(yè)內(nèi)部,會有一些約定俗成的業(yè)務(wù)模型和流程,這些東西如何復(fù)用,一直是一個難題。以往的做法,會把這些東西都放在服務(wù)端,用類似Java這樣的語言來實現(xiàn)業(yè)務(wù)元素、業(yè)務(wù)規(guī)則和業(yè)務(wù)流程的管理。這種做法所帶來的一個缺點就是對界面層的忽視,因為他只把界面層當(dāng)作展示,對其中可能出現(xiàn)的大量JavaScript邏輯感到無所適從。很多從事這一領(lǐng)域的架構(gòu)師不認同界面層的厚度,他們認為這一層只應(yīng)當(dāng)是很薄的,純展示相關(guān)的,但在這個時代,已經(jīng)不存在真正輕量級的界面了。 前面提到,我們在前端作分層,把展現(xiàn)層跟業(yè)務(wù)邏輯層完全隔離,帶來的好處就是邏輯層不存在對DOM的操作,只有純粹的邏輯和遠程調(diào)用,這么一來,這一層的東西都可以很容易做測試。對于一個大型產(chǎn)品來說,持續(xù)集成是很有必要的,自動化測試是持續(xù)集成中不可缺少的一環(huán)。如果不做分層,這個測試可能就比較難做,現(xiàn)在我們能把容易的先做掉,而且純邏輯的代碼,還可以用更快的方式來測試。 之前我們做前端的單元測試,都需要把代碼加載到瀏覽器來執(zhí)行,或者自行封裝一些“無頭瀏覽器”,也就是不打開實際的展示,模擬這個測試過程。這個過程相對來說還是有些慢,因為它還有加載的這個網(wǎng)絡(luò)傳輸?shù)倪^程,如果我們能在服務(wù)端做這個事情呢?我們看到,最近很火的NodeJS,它從很多方面給了前端工程師一個機會,去更多地把控整個開發(fā)流程,在我們這個場景下,如果能把針對前端邏輯的單元測試都放在node里做,那效率就會更高。 二次開發(fā)平臺我們來看看,有了這么一套分層機制,又有了界面標(biāo)簽庫之后,該做些什么呢?做企業(yè)軟件的公司,有不少會做二次開發(fā)平臺,這個平臺的目標(biāo)是整合一些已有的行業(yè)組件,讓業(yè)務(wù)開發(fā)人員甚至是不懂技術(shù)的業(yè)務(wù)人員通過簡單的拖拉、配置的形式,組合生成新的業(yè)務(wù)功能。 從界面的角度看,拖拽生成很容易,很多界面原型工具都可以做,但要如何整合數(shù)據(jù)和業(yè)務(wù)?因為你要生成的這個功能,是實實在在要拿去用,不是有個樣子看就可以,所以要能跟真實數(shù)據(jù)結(jié)合起來。 但這事情談何容易!就比如說,界面上有一個選擇所屬行業(yè)的下拉框,里面數(shù)據(jù)是配置出來的,對這個數(shù)據(jù)的查詢操作在后端,作為一個查詢服務(wù)或者是業(yè)務(wù)對象管理起來,有些傳統(tǒng)的方式可能是在后端作這個關(guān)聯(lián),Angular框架可以把這個事情推到前端來。相比Backbone這樣的框架來說,Angular由于有雙向綁定,這個過程會變得特別省事。一個界面片段想要和數(shù)據(jù)關(guān)聯(lián)起來,要做的事情就是各種屬性的設(shè)置,所以動態(tài)加載和動態(tài)綁定都會比較容易。 比如: partial.html <ul> <li ng-repeat="item in items">{{item.name}}</li> </ul> main.html ... <div ng-include="'partial.html'" ng-controller="CtrlA"></div> ... a.js function CtrlA($scope) { $scope.items = [{name:"Tom"}, {name:"Jerry"}]; } b.js function CtrlB($scope) { $scope.items = [{name:"Donald"}, {name:"Micky"}]; } 在上面的例子里,這個列表顯示什么,完全取決于ng-controller="CtrlA"這句,如果我們把這句搞成配置的,就很容易把數(shù)據(jù)源換成另外一個CtrlB,甚至說,即使在同一版本上做項目化,引入另外一個包含CtrlA其他版本的js文件,也基本無需更改其他代碼,這就達到了二次開發(fā)的一個目的:盡可能以配置而不是編碼去新增、維護新功能。 移動開發(fā)現(xiàn)在的企業(yè)軟件已經(jīng)不能只考慮PC的瀏覽器了,很多客戶都會有移動辦公的需求。響應(yīng)式設(shè)計是一種常見的解決方案,但是在企業(yè)應(yīng)用領(lǐng)域,想要把復(fù)雜的業(yè)務(wù)功能設(shè)計成響應(yīng)式界面的代價太大了,況且界面設(shè)計本身就是開發(fā)企業(yè)軟件的這些公司的短板,所以我們的比較簡單的辦法是對PC和移動終端單獨設(shè)計界面,這樣就有了一個問題了,這兩種界面的業(yè)務(wù)邏輯并沒有差別,如果我們要維護兩套代碼,代價是非常大的,能有什么辦法共用一些東西呢? 如果不采用分層的形式,那這個很麻煩,我們注意到兩種系統(tǒng)的差異只在UI層,如果我們用分層的模式,可以共用UI層以外的東西。具體到Angular里面來說,比如service,factory,甚至controller都是可以共用的,只有directive和HTML模板隨設(shè)備產(chǎn)生差異就可以了。 之前我們很少看到有基于Angular的移動端開發(fā)框架,但現(xiàn)在有了,比如Ionic,使用這樣的框架,可以直接引用已有的業(yè)務(wù)邏輯代碼,只在展示上作一些調(diào)整。這么做有很多好處,同時也對代碼的架構(gòu)水準有一定要求,需要把業(yè)務(wù)邏輯跟界面展示完全切割開。 這樣帶來的好處也是很明顯的,獨立的業(yè)務(wù)邏輯,因為它不依賴于界面了,所以很容易控制,做單元測試,集成測試,打樁等等,總之它是純邏輯的東西,在后端可以用什么方式保證代碼質(zhì)量,在前端的業(yè)務(wù)邏輯也一樣可以用,業(yè)務(wù)邏輯可以因此而清晰穩(wěn)定。對于企業(yè)應(yīng)用而言,這么做可以極大程度地復(fù)用以往的業(yè)務(wù)邏輯,只在負責(zé)最終展示的代碼部分作差異化。 工程化上面這些技術(shù)性的問題都解決了,剩下的都是規(guī)模帶來的邊際效應(yīng),這需要我們從工程化角度去考慮很多問題:
這些話題,篇幅所限,不在本文中敘述,可以查看我另外的關(guān)于Web應(yīng)用組件化的文章。 |
|