基於AngularJS的企業軟體前端架構

民工精髓發表於2014-04-28

這篇是我參加QCon北京2014的演講內容:

提綱:

企業應用在軟體行業中佔有很大的比重,而這類軟體多數現在也都採用B/S的模式開發,在這個日新月異的時代,它們的前端開發技術找到了什麼改進點呢?

B/S企業軟體前端開發模式大體上與桌面軟體類似,都是偏重量級的,在前端可能會有較多的業務邏輯,這些業務邏輯如何被合理模組化,與介面分離,以便測試,成為這個領域的一個重要挑戰。另一方面,由於企業應用的介面相對規整,偏重的是資料存取,沒有太多花哨的東西,所以常見的介面控制元件也是可列舉的,如何讓開發介面的工作能更快完成,甚至由不擅長編寫程式碼的業務設計人員來做,與介面原型的工作合二為一,能提高不少開發效率。

在AngularJS等MV*框架出現之後,給這個領域帶來一些契機,架構師們能夠有機會去重新規劃前端的架構,甚至是開發流程,從而讓整個軟體的生產更為高效。

本文將探討它給這個領域帶來的變化。

正文:

企業應用前端的特點

企業應用系統是一種很常見的軟體系統,這類系統的特點是面向某個行業,功能較複雜,對介面的要求一般是整齊,不追求花哨。這類系統通常有C/S和B/S兩個流派,其中的B/S方式因為部署和整合的便利,使用得較為普遍。

同樣是在瀏覽器中做東西,寫企業應用和網站的差別也很明顯。企業應用的業務邏輯較重,前端有一定的厚重性,但是對效果並不追求很多,主要是各類控制元件的使用,表單的存取值等等。

企業應用產品的一些特點如下:

  • 獨佔模式。

一般使用者使用網際網路產品,都是片段時間使用,比如購物或者閱讀,做完之後就重新整理或者關閉瀏覽器了,而企業應用往往是工作的全部,從早上上班開始開啟,到下班才關掉,一天絕大部分工作都在上面完成,比如一個呼叫中心的操作員。

  • 重業務,輕視覺

企業應用對視覺的追求是比較低的,一般不會要求花哨效果,以業務操作的流暢性為第一目標。

  • 介面規整,模式單一

企業應用的介面佈局相對有模式可循,可以用很少的場景來窮舉,介面橫平豎直,比較規整,使用到的控制元件元素也是可窮舉的,基本沒有什麼特效。

  • 鍵盤操作

由於企業應用的使用者都相對比較專業,在上崗之前需要經過統一培訓,而且每個使用者使用的頻度較高,很多時候他們會用盡量快捷的方式來做操作,比如鍵盤,這一點在網際網路產品中比較少見。所以,有時候大家為了追求好看,把系統原生的select用div來替換,在這種情況下反而增加了使用者的麻煩。

  • 邏輯複雜

我之前所在的行業中,業務邏輯很複雜,前端可能會需要寫很多複雜的邏輯,JS程式碼大部分是在處理邏輯,而不是介面互動。

  • 載入速度的側重不同

網際網路產品往往很重視首屏優化,但是其策略可能與企業應用不同。比如說,3個200k的模組,在網站型產品中可能優化成一個100k加三個150k的模組,但在企業應用中,很可能優化成一個400k加三個50k的模組。為什麼會這樣呢?因為內容型的網站講究的優化策略是分攤,如果首次載入太慢,會很影響使用者的信心,但企業應用使用者的容忍度是較高的,他並不在乎剛開啟的時候慢一些,因為開啟了之後就要用一天,對於之後每步操作的模組載入速度倒是要求很高。另外,對於記憶體洩露的處理,也要求得比較高一些。整個這些策略,其實是來源於C/S系統的影響。

  • 瀏覽器版本相對寬鬆

很多時候提到企業應用,大家的想法就是低端,IE6,但其實這個的原因是客戶只購買軟體,運維一般自己做,每年不會有很多持續的投入來改進,所以導致很多老系統不能持續升級。軟體廠商其實反倒可以用更激進的策略去升級瀏覽器,使用者對這個的接受度還是比較高的,使用系統的群體也是比網際網路使用者小很多的,拋棄老舊瀏覽器的事情也確實可以幹,比如我就見過幾年前某電信營業系統預裝的都是Firefox。

企業應用常見的前端框架

在開發B/S企業應用前端的人群中,有很大一部分群體選擇了服務端的元件化方式,比如JSF之類,它的弊端是與異構服務端的第三方系統整合比較麻煩。也有不少人使用Bindows和ExtJS這樣的框架,最近的KendoUI也是個不錯的選擇。

每種型別選一個有代表性的來說說:

  • HTC 在瀏覽器端擴充套件標籤

早期有些團隊採用的方式,一般會跟XMLHTTP等結合使用,易於使用,介面程式碼整潔,但已被主流瀏覽器拋棄。

  • JSF等 在服務端生成介面

以後端為主的架構師最推崇的方式,受Struts的MVC模型影響很深,弱化了前端,使得前端蛻化為後端的一種附屬。

  • GWT 編譯階段生成介面

寫其他語言來生成HTML和JS,一般會依賴於一種前端UI庫。這種方式也比較受後端架構師喜歡,因為他們覺得寫JS很頭疼,寧可寫Java。

  • ExtJS 用JS封裝介面元件,乾脆就不要HTML了

這是另外一種極端,從Bindows開始,使用純邏輯程式碼來描述介面,走著跟Java Swing一樣的道路,也有不少人喜歡。但這種方式在沒有好用的介面設計器的情況下非常痛苦。

  • Flex等 脫離HTML體系,另闢蹊徑

這條路其實是對Java Applet的一種延續,好處是可以不受HTML體系的制約,獨立發展,所以其實這些體系在企業應用領域的成熟度遠超HTML體系。

曾經的企業B/S應用幾件寶

有一段時間,我們幾乎只有IE6,所以那個時候的前端開發人員很快樂,沒有相容的壓力。那時候,我們如何構建前端應用呢?

參見http://weibo.com/1858846672/B1fL3vuYN?mod=weibotime

  • HTC

這是最好用的宣告控制元件的方式。

  • XMLHTTP

儘管還沒有AJAX的概念,但我們已經可以用它做前後端分離的傳輸機制了。

  • VML

在IE裡面畫向量圖,不使用外掛,有其他選擇嗎?

  • XSLT

把XML資料轉換成HTML,跟現在的前端模板像嗎?

  • popup

建立右鍵選單最好的方式。

用這些技術構建的一個典型企業應用

單頁應用和前端分層

當時這些系統的構建方式也可以算單頁應用,我們用iframe來整合選單,每個選單有自己獨立的功能,整個主介面是始終不會重新整理的。

時光飛逝,這些年,前端有了什麼本質的改變,產生了翻天覆地的變化嗎?

有時候我們回顧一下,卻發現多數都是在增加完善一些細節,真正有顛覆性的有比如以RequireJS和SeaJS為代表的模組定義和載入庫,npm這樣的包管理器,grunt,gulp,百度fis這樣的整合開發模式。為什麼它們算是本質改進呢?

因為這些標誌著前端開發從粗放的模式,逐漸變化到精確控制的形態。比如我們再也不能不管程式碼的依賴關係,也不能一開啟介面就不分青紅皁白把所有可能要用到的程式碼都立刻載入過來,那個時代已經過去了,從任何角度講,現代的前端開發都在精細化,從程式碼的可控,到介面體驗的精細優化,到整個團隊甚至公司甚至網際網路上的元件共享,以及前端團隊協作流程的改進,這已經是一個很成規模的產業了。

我們把眼光放到2013年,在這一年裡最火的前端技術莫過於NodeJS和AngularJS,前者給我們帶來的是一種開發方式的改變,後者是一種典型的前端分層方案。Angular是前端MV*框架的一個流派,用過的人都會覺得很爽。它爽在什麼地方呢?因為它幫我們做的事情太多了,一個雙向繫結,無所不包,凡是存取值相關的操作,基本都不用自己寫程式碼。在企業應用前端功能裡,表單的存取值和校驗佔據了很大的比例,這些事都不用幹了,那簡直太好了。

如果就因為這個用Angular,那還有些早。有一些第三方程式碼被稱為庫,另外一些稱為框架,Angular是框架而不是庫。框架的含義是,有更強的約束性,並非作為輔助功能來提供的。

先看一下企業應用的通常形態吧,會有一個可配置的選單,然後多半會採用MDI的形式,能開啟多個業務功能,用選項卡的形式展示起來,可以隨時切換操作。每個人每天常用的功能是可以窮舉的,他進入系統之後,一般要用到下班才關掉。所以這種系統非常適合做成單頁應用,開始的時候載入一個總體框架,每點選一個選單,就載入這個選單對應的功能模組,放在一個新的選項卡或者別的什麼地方展示出來。

在早期做這種系統的時候,一般都會用iframe來整合選單,這種方式很方便,但是每個選單頁都要載入共同的框架檔案,初始化一個環境,資料之間也不能精確共用。

所以現在我們做企業資訊系統,不再適合用iframe來整合選單,所有選單的業務程式碼,會在同一個頁面的作用域中共存。這在某些方面是便利,比如資料的共享,一個選擇全國城市的下拉框,在多個功能中都存在,意味著這些城市的資料我們可以只載入一次。但從另外一個角度來說,也是一種挑戰,因為資料之間產生干擾的可能性大大增加了。

我們回顧一下在傳統的客戶端開發中是怎麼做的,早在經典的《設計模式》一書中,就提到了MVC模式,這是一種典型的分層模式。長期以來,在Web開發人員心中的MVC,指的都是Struts框架的那張圖,但我們單頁應用中的MVC,其實更接近最原始的《設計模式》書中概念。所以我們要在前端分層,而不僅僅把整個前端都推到檢視層。

做單頁應用,前端不分層是很難辦的,當規模擴大的時候,很難處理其中一些隱患。分層更重要的好處是能夠從全盤考慮一些東西,比如說資料的共享。跨模組的資料共享是一個比較複雜的話題,搞得不好就會導致不一致的情況,如果考慮到在分層的情況下,把各種資料來源都統一維護,就好辦多了。

所以,以AngularJS為代表的前端MV*框架最重要的工作就是做了這些對於分層的指導和約束性工作,在此基礎上,我們可以進一步優化單頁應用這類產品。

前端的自定義標籤體系

構建一個大型企業應用,最重要的是建立整套元件體系。一般針對某行業的軟體,長期下來都會有很多固定的模式,可以提煉成元件和規則,從前端來看,體現為控制元件庫和前端邏輯。控制元件庫這個是老生常談,在很多框架裡都有這個概念,但各自對應的機制是不同的。

從寫一個介面的角度來講,最為便利的方式是基於標籤的宣告式程式碼,比如我們常見的HTML,還有微軟的XAML,Flex中的MXML等,都很直接,設想一下在沒有視覺化IDE的情況用類似Java Swing和微軟WinForm這樣的方式編寫介面,毫無疑問寫XML的方式更易被接受。所以,我們可以得出初步的結論,介面的部分應該寫標籤。

很遺憾,HTML自帶的標籤是不足的,它有基本表單輸入控制元件,但是缺乏DataGrid,Tree之類更富有表現性的控制元件。所以絕大多數介面庫,都採用某種使用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();
});

如果這樣,這些複雜控制元件就都要通過JavaScript來建立和渲染了,這與我們剛才提到的原則是違背的。那我們尋找的是什麼呢,是一種能擴充套件已有HTML體系的東西。在早期,IE瀏覽器中有HTC,可以通過引入名稱空間來宣告元件,現在的標準瀏覽器中又引入了Web Components,在Polymer這個框架中可以看到更多的細節。說到底,這類方式要做些什麼事情呢?

  • 隔離元件的實現,讓使用變得簡單
  • 支援自行擴充套件新的元件
  • 作一些作用域上的隔離,比如Web Components裡面,style標籤上可以加作用域,表示這個樣式只生效於元件內部

從另外一個角度講,為什麼我們非要這麼做不可?最大好處來自哪裡?對於大型專案而言,管理成本和變更成本都是需要認真考慮的。如果一個元件,需要在DOM中宣告一個節點, 然後再用一個js去獲取DOM,把DOM渲染出來,再填充資料的話,這個過程的管理成本是很大的,因為HTML和JS這兩個部分丟了一個都會有問題,無論在什麼時候,維護一個檔案總是比維護多個檔案要強的,我們看HTC那種方式,為什麼它的使用成本很低,因為它可以把控制元件自身的DOM、邏輯、樣式全部寫在自己內部,整個一個檔案被人引用就可以了。在現在這個階段不存在這麼好用的技術了,只能退而求其次。

所以,在這個點上,Angular帶來的好處是可擴充套件的標籤體系,這也就是標籤的語義化。Angular的主打功能之一是指令,使用這種方式,可以很容易擴充套件標籤或者屬性。比如,業務開發人員可以直接寫:

<panel>
     <tree data="{{data}}"></tree>
</panel>

這樣多麼直觀,而且可以跟原有的HTML程式碼一起編寫,不造成任何負擔。語義化的標籤是快速編寫介面的不二法門。

業務邏輯

有了語義化標籤之後,如果我們只寫介面不寫邏輯,那也夠了,但現實往往沒有這麼美好,我們還要來考慮一下業務邏輯怎麼辦。

企業應用一般都是面向某行業的,在這個行業內部,會有一些約定俗成的業務模型和流程,這些東西如何複用,一直是一個難題。以往的做法,會把這些東西都放在服務端,用類似Java這樣的語言來實現業務元素、業務規則和業務流程的管理。

這種做法所帶來的一個缺點就是對介面層的忽視,因為他只把介面層當作展示,對其中可能出現的大量JavaScript邏輯感到無所適從。很多從事這一領域的架構師不認同介面層的厚度,他們認為這一層只應當是很薄的,純展示相關的,但在這個時代,已經不存在真正輕量級的介面了。

前面提到,我們在前端作分層,把展現層跟業務邏輯層完全隔離,帶來的好處就是邏輯層不存在對DOM的操作,只有純粹的邏輯和遠端呼叫,這麼一來,這一層的東西都可以很容易做測試。對於一個大型產品來說,持續整合是很有必要的,自動化測試是持續整合中不可缺少的一環。如果不做分層,這個測試可能就比較難做,現在我們能把容易的先做掉,而且純邏輯的程式碼,還可以用更快的方式來測試。

之前我們做前端的單元測試,都需要把程式碼載入到瀏覽器來執行,或者自行封裝一些“無頭瀏覽器”,也就是不開啟實際的展示,模擬這個測試過程。這個過程相對來說還是有些慢,因為它還有載入的這個網路傳輸的過程,如果我們能在服務端做這個事情呢?

我們看到,最近很火的NodeJS,它從很多方面給了前端工程師一個機會,去更多地把控整個開發流程,在我們這個場景下,如果能把針對前端邏輯的單元測試都放在node裡做,那效率就會更高。

二次開發平臺

我們來看看,有了這麼一套分層機制,又有了介面標籤庫之後,該做些什麼呢?

做企業軟體的公司,有不少會做二次開發平臺,這個平臺的目標是整合一些已有的行業元件,讓業務開發人員甚至是不懂技術的業務人員通過簡單的拖拉、配置的形式,組合生成新的業務功能。

從介面的角度看,拖拽生成很容易,很多介面原型工具都可以做,但要如何整合資料和業務?因為你要生成的這個功能,是實實在在要拿去用,不是有個樣子看就可以,所以要能跟真實資料結合起來。 但這事情談何容易!

就比如說,介面上有一個選擇所屬行業的下拉框,裡面資料是配置出來的,對這個資料的查詢操作在後端,作為一個查詢服務或者是業務物件管理起來,有些傳統的方式可能是在後端作這個關聯,Angular框架可以把這個事情推到前端來。相比Backbone這樣的框架來說,Angular由於有雙向繫結,這個過程會變得特別省事。一個介面片段想要和資料關聯起來,要做的事情就是各種屬性的設定,所以動態載入和動態繫結都會比較容易。

比如:

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"這句,如果我們把這句搞成配置的,就很容易把資料來源換成另外一個CtrlB,甚至說,即使在同一版本上做專案化,引入另外一個包含CtrlA其他版本的js檔案,也基本無需更改其他程式碼,這就達到了二次開發的一個目的:儘可能以配置而不是編碼去新增、維護新功能。

移動開發

現在的企業軟體已經不能只考慮PC的瀏覽器了,很多客戶都會有移動辦公的需求。響應式設計是一種常見的解決方案,但是在企業應用領域,想要把複雜的業務功能設計成響應式介面的代價太大了,況且介面設計本身就是開發企業軟體的這些公司的短板,所以我們的比較簡單的辦法是對PC和移動終端單獨設計介面,這樣就有了一個問題了,這兩種介面的業務邏輯並沒有差別,如果我們要維護兩套程式碼,代價是非常大的,能有什麼辦法共用一些東西呢?

如果不採用分層的形式,那這個很麻煩,我們注意到兩種系統的差異只在UI層,如果我們用分層的模式,可以共用UI層以外的東西。具體到Angular裡面來說,比如service,factory,甚至controller都是可以共用的,只有directive和HTML模板隨裝置產生差異就可以了。

之前我們很少看到有基於Angular的移動端開發框架,但現在有了,比如Ionic,使用這樣的框架,可以直接引用已有的業務邏輯程式碼,只在展示上作一些調整。這麼做有很多好處,同時也對程式碼的架構水準有一定要求,需要把業務邏輯跟介面展示完全切割開。

這樣帶來的好處也是很明顯的,獨立的業務邏輯,因為它不依賴於介面了,所以很容易控制,做單元測試,整合測試,打樁等等,總之它是純邏輯的東西,在後端可以用什麼方式保證程式碼質量,在前端的業務邏輯也一樣可以用,業務邏輯可以因此而清晰穩定。

對於企業應用而言,這麼做可以極大程度地複用以往的業務邏輯,只在負責最終展示的程式碼部分作差異化。

工程化

上面這些技術性的問題都解決了,剩下的都是規模帶來的邊際效應,這需要我們從工程化角度去考慮很多問題:

  • 某個JS模組被修改,如何得知會影響誰?
  • 某個介面片段被調整,會影響什麼介面?
  • 如何最小化釋出?
  • 如何一鍵測試、打包、壓縮?
  • 。。。。。。

這些話題,篇幅所限,不在本文中敘述,可以檢視我另外的關於Web應用元件化的文章。

相關文章