企業應用架構模式(摘抄)

—選自《企業應用架構模式》

目 錄前端

1 引言程序員

 0.1 架構數據庫

 0.2 企業應用瀏覽器

 0.3 企業應用的種類性能優化

 0.4 關於性能的考慮服務器

 0.5 模式網絡

  0.5.1 模式的結構多線程

  0.5.2 模式的侷限性架構

1引言

構建計算機系統並不是易事。隨着系統複雜性的增大,構建相應軟件的難度將呈指數增大。同其餘行業同樣,咱們只有在不斷的學習中進步,從成功經驗中學習,從失敗教訓中學習,纔有望克服這些困難。本書的內容就是這樣一些「學習」經驗。我但願它們的撰寫和編排方式,可以有助於讀者更快地學習這些內容,而且,和我在總結出這些模式以前相比,能更有效地與他人進行交流。app

在引用中,我想設定本書討論的範圍,並提供一些相關的背景知識與材料。 

0.1 架構

軟件業的人樂於作這樣的事——找一些詞彙,並把它們引伸到大量微妙而又互相矛盾的含義。一個最大的受害者就是「架構」(architecture)這個詞。我我的對「架構」的感受是,它是一個讓人印象深入的詞,主要用來表示一些很是重要的東西。固然,我也會當心,不讓這些對「系統結構」的「不恭之詞」,影響到讀者對本書的興趣。

不少人都試圖給「架構」下定義,而這些定義自己卻很難統一。可以統一的內容有兩點:一點是「最高層次的系統分解」;另外一點是「系統中不易改變的決定」。愈來愈多的人發現:表述一個系統架構的方法不僅一種;一個系統中也可能有不少種不一樣的架構,並且,對於什麼在架構上意義重大的見解也會隨着系統的生命週期變化。

Ralph Johnson常常在郵件列表上發帖,並提出一些使人關注的看法。就在我完成本書初稿的同時,他又發表了一些關於「架構」的觀點。他認爲,架構是一種主觀上的東西,是專家級項目開發人員對系統設計的一些可共享的理解。通常地,這種可共享的理解表現爲系統中主要的組成部分以及這些組成間的交互關係。它還包括一些決定,開發者們但願這些決定能及早作出,由於在開發者看來它們是難以改變的。架構的主觀性也來源於此——若是你發現某些決定並不像你想象的那麼難以改變,那麼它就再也不與架構相關。到了最後,架構天然就濃縮成一些重要的東西,不論這些東西是什麼。

在本書中,我提出一些本身的理解,涉及企業應用主要組成部分和我但願能儘早作出的決定。在這些架構模式中,我最欣賞的就是「層次」,將在第1章中進行詳細介紹。全書實際上就是關於如何將企業應用組織成不一樣的層次,以及這些層次之間如何協同工做。大多數重要的企業應用都是按照某種形式的層次分層設計的;固然,在某些狀況下,別的設計方式(如管道方式、過濾器方式等)也有它們本身的價值。在本書中咱們將不會討論這些方式,而把注意力集中在層次方式上,由於它是應用最廣的設計方式。

本書中的一些模式毫無疑問是關於架構的,它們表示了企業應用各主要組成部分間的重要決定;另一些模式是關於設計的,有助於架構的實現。我沒有刻意區分這兩類模式,由於正如咱們前面討論的,是否與架構相關每每帶有主觀性。

0.2 企業應用

編寫計算機軟件的人不少,咱們一般把這些活動都稱爲軟件開發。可是軟件的種類是不一樣的,每種軟件都有自身的挑戰性和複雜性。我是在與幾個從事電信軟件開發的朋友交談後,意識到這個問題的。企業應用在某些方面要比電信軟件簡單得多——多線程問題沒有那麼困難,無需關注硬件設備與軟件的集成。可是,在某些方面,企業應用又比電信軟件複雜得多——企業應用通常都涉及到大量複雜數據,並且必須處理不少「不合邏輯」的業務規則。雖然有些模式是適合全部軟件的,可是大多數模式都還只適合某些特定的領域和分支。

個人工做主要是關於企業應用的,所以,這裏所談及的模式也都是關於企業應用的。(企業應用還有一些其餘的說法,如「信息系統」或更早期的「數據處理」。)那麼,這裏的「企業應用」具體指的是什麼呢?我沒法給出一個精確的定義,可是我能夠羅列一些我的的理解。

先舉幾個例子。企業應用包括工資單、患者記錄、發貨跟蹤、成本分析、信譽評估、保險、供應鏈、記帳、客戶服務以及外幣交易等。企業應用不包括車輛加油、文字處理、電梯控制、化工廠控制器、電話交換機、操做系統、編譯器以及電子遊戲等。

企業應用通常都涉及到持久化數據。數據必須持久化是由於程序的屢次運行都須要用到它們——實際上,有些數據須要持久化若干年。在此期間,操做這些數據的程序每每會有不少變化。這些數據的生命週期每每比最初生成它們的那些硬件、操做系統和編譯器還要長。在此期間,數據自己的結構通常也會被擴展,使得它在不影響已有信息的基礎上,還能表示更多新信息。即便是有根本性的變化發生,或公司安裝了一套全新的軟件,這些數據也必須被「遷移」到這些全新的應用上。

企業應用通常都涉及到大量數據——一箇中等規模的系統每每都包含1GB以上的數據,這些數據是以百萬條記錄的方式存在的。巨大的數據量致使數據的管理成爲系統的主要工做。早期的系統使用的是索引文件系統,如IBM的VSAM和ISAM。現代的系統每每採用數據庫,絕大多數是關係型數據庫。數據庫的設計和演化已使其自己成爲新的技術領域。

企業應用通常還涉及到不少人同時訪問數據。對於不少系統來講,人數可能在100人如下,可是對於一些基於Web的系統,人數會呈指數級增加。要確保這些人都可以正確地訪問數據,就必定會存在這樣或那樣的問題。即便人數沒有那麼多,要確保兩我的在同時操做同一數據項時不出現錯誤,也是存在問題的。事務管理工具能夠處理這個問題,可是它一般沒法作到對應用開發者透明。

企業應用還涉及到大量操做數據的用戶界面屏幕。有幾百個用戶界面是不足爲奇的。用戶使用頻率的差別很大,他們也常常沒什麼技術背景。所以,爲了避免同的使用目的,數據須要不少種表現形式。系統通常都有不少批處理過程,當專一於強調用戶交互的用例時,這些批處理過程很容易被忽視。

企業應用不多獨立存在,一般須要與散佈在企業周圍的其餘企業應用集成。這些各式各樣的系統是在不一樣時期,採用不一樣技術構建的,甚至連協做機制都不一樣:COBOL數據文件、CORBA系統或是消息系統。企業常常但願能用一種統一的通訊技術來集成全部系統。固然,每次這樣的集成工做幾乎都很難真正實現,全部留下來的就是一個個風格各異的集成環境。當商業用戶須要同其業務夥伴進行應用集成時,狀況就更糟糕。

即便是某個企業統一了集成技術,它們也仍是會遇到業務過程當中的差別以及數據中概念的不一致性。一個部分可能認爲客戶是當前簽有協議的人;而另一個部門可能還要將那些之前有合同,但如今已經沒有了的人計算在內。再有,一個部門可能只關心產品銷售而不關心服務銷售。粗看起來,這些問題彷佛容易解決,可是,一旦幾百個記錄中的每一個字段都有可能存在着細微差異,問題的規模就會造成不小的挑戰——就算惟一知道這些字段之間差異的員工還在公司任職(固然,也許他在你察覺到以前就早已辭職不幹了)。這樣,數據就必須被不停地讀取、合併、而後寫成各類不一樣語法和語義的格式。

再接下來的問題是由「業務邏輯」帶來的。我認爲「業務邏輯」這個詞很滑稽,由於很難找出什麼東西比「業務邏輯」更加沒有邏輯。當咱們構建一個操做系統時,老是儘量地使得系統中的各類事物符合邏輯。而業務邏輯生來就是那樣的,沒有至關的行政努力,不要想改變它,固然,它們都有本身的理由。你必須面對不少奇怪的條件。並且這些條件相互做用的方式也很是怪異。好比,某個銷售人員爲了簽下其客戶幾百萬美圓的一張單,可能會在商務談判中與對方達成協議,將該項目的年度到帳時間推遲兩天,由於這樣才能與該客戶的帳務週期相吻合。成千上萬的這類「一次特殊狀況」最終致使了複雜的業務「無邏輯」,使得商業軟件開發那麼困難。在這種狀況下,必須儘可能將這些業務邏輯組織成有效的方式,由於咱們能夠肯定的是,這些「邏輯」必定會隨着時間不斷變化。

對於一些人來講,「企業應用」這個詞指的是大型系統。可是 須要注意的是,並非全部的企業應用都是大型的,儘管它們可能都爲企業提供巨大的價值。不少人認爲,因爲小型系統的規模不大,能夠不用太注意它們,並且在某種程度上,這種觀點可以帶來必定的成本節約。若是一個小型系統失敗了,相對於大型系統的失敗,這種失敗就不會顯得那麼起眼了。可是,我認爲這種思想沒有對小型項目的累積做用給予足夠的重視。試想,若是在小型項目上可以進行某些改善措施,那麼一旦這些改善措施被成功運用於大型項目,它帶來的效果就會很是大。實際上,最好是經過簡化架構和過程,將一個大型項目簡化成小型項目。

0.3 企業應用的種類

在咱們討論如何設計企業應用以及使用哪些模式以前,明確這樣一個觀點是很是重要的,即企業應用是多種多樣的,不一樣的問題將致使不一樣的處理方法。若是有人說「老是這樣作」 的時候,就應該敲響警鐘了。我認爲,設計中最具挑戰性(也是我最感興趣)的地方就是了解有哪些候選的設計方法以及各類不一樣設計方法之間的優劣比較。進行選擇的控件很大,但我在這裏只選三個方面。

考慮一個B2C(Business to Customer)的網上零售商:人們經過瀏覽器瀏覽,經過購物車購買商品。經過購物車購買商品。這樣一個系統必須可以應付大量的客戶,所以,其解決方案不但要考慮到資源利用的有效性,還要考慮到系統的可伸縮性,以便在用戶規模增大時可以經過增長硬件的辦法加以解決。該系統的業務邏輯能夠很是簡單:獲取訂單,進行簡單的價格計算和發貨計算,給出發貨信息。咱們但願任何人都可以訪問該系統,所以用戶界面能夠選用通用的Web表現方式,以支持各類不一樣的瀏覽器。數據源包括用來存放訂單的數據庫,還可能包括某種與庫存系統的通訊交流,以便得到商品的可用性信息和發貨信息。

再考慮一個租約合同自動處理系統。在某些方面,這樣的系統比起前面介紹的B2C系統要簡單,由於它的用戶數不多(在特定時間內不會超過100個),可是它的業務邏輯卻比較複雜。計算每一個租約的月供,處理如提前解約和延遲付款這樣的事件,簽定合同時驗證各類數據,這些都是很是複雜的任務,由於租約領域的許多競爭都是以過去的交易爲基礎稍加變化而出現的。正是由於規則的隨意性很大,才使得像這樣一個複雜領域具備挑戰性。

這樣的系統在用戶界面(UI)上也很複雜。這就要求HTML界面要能提供更豐富的功能和更復雜的屏幕,而這些要求每每是HTML界面目前沒法達到的,須要更常規的胖客戶界面。用戶交互的複雜性還會帶來事務行爲的複雜性:簽定租約可能要耗時1~2個小時,這期間用戶要處於一個邏輯事務中。一個複雜的數據庫設計方案中可能也會涉及到200多個表以及一些有關資產評估和計價的軟件包。

第三個例子是一家小型公司使用的簡單的「開支跟蹤系統」。這個系統的用戶不多,功能簡單,經過HTML表現方式能夠很容易實現,涉及的數據源表項也很少。儘管如此,開發這樣的系統也不是沒有挑戰。一方面你必須快速地開發出它,另外一方面你又必須爲它之後可能的發展考慮;也許之後會爲它增長賠償校驗的功能,也許它會被集成到工資系統中,也許還要增長關於稅務的功能,也許要爲公司的CFO生成彙總報表,也許會被集成到一個航空訂票Web Service中,等等。若是在這個系統的開發中,也試圖使用前面兩個例子中的一些架構,可能會影響開發進度。若是一個系統會帶來業務效益(如全部的企業應用應該的那樣),則系統進度延誤一樣也是開銷。若是如今不作決策又有可能影響系統將來的發展。可是,若是如今就考慮了這些靈活性可是考慮不得當,額外的複雜性又可能會影響到系統的發展,進一步延誤系統部署,減小系統的效益。雖然這類系統很小,可是一個企業中每每有不少這樣的系統,這些系統的架構不良性累積起來,後果將會很是可怕。

這三個企業應用的例子都有難點,並且難點各不相同。固然,也不可能有一個適合於三者的通用架構。選擇架構時,必須很清楚地瞭解面臨的問題,在理解的基礎上再來選擇合適的設計。本書中也沒有一個通用的解決方案。實際上,不少模式僅僅是一些可選方案罷了。即便你選擇了某種模式,也須要進一步根據面臨的問題來修改模式。在構建企業應用時,你不思考是不行的。全部書本知識只是給你提供信息,做爲你作決定的基礎。

模式是這樣,工具也一樣如此。在系統開發時應該選取儘量少的工具,同時也要注意,不一樣的工具擅長處理的方面也不一樣,切記不要用錯了工具,不然只會事倍功半。

0.4 關於性能的考慮

不少架構的設計決策和性能有關。對於大多數與性能相關的問題,個人辦法是首先創建系統,調試運行,而後經過基於測量的嚴格的優化過程來提升性能。可是,有一些架構上的決策對性能的影響,多是後期優化難以彌補的。並且即便這種影響能夠在後期很容易地彌補,參與這個項目的人們任然會從一開始就擔憂這些決策。

在這樣的一本書中討論性能一般很困難。這是由於「眼見爲實」:全部那些關於性能的條條框框,不在你的具體系統中配置運行一下,是很難有說服力的。我也常常看到一些設計方案由於性能方面的考慮而被接受或拒絕,可是一旦有人在真實的設置環境中作一些測量,就會證實這些考慮是錯誤的。

本書將提出一些這方面的建議,包括儘可能減小遠程調用(它在很長時間內都被認爲是優化性能的好建議)。儘管如此,仍是建議讀者在運用這些原則以前,在你的應用中具體試一試。一樣,本書中的樣例代碼也有一些地方爲了提升可讀性而犧牲了效率。在你的系統中,須要自行決定是否進行優化。在作性能優化後,必定要與優化前進行測量對比,以肯定真的獲得了優化,不然,你可能只是破壞了代碼的可讀性。

還有一個很重要的推論:配置上的重大變化會使得某些性能優化失效。所以,在升級虛擬機、硬件、數據庫或其餘東西到新的版本時,必須從新確認性能優化工做的有效性。不少狀況下,配置變動都會對性能優化有影響,有時候你真的會發現,之前爲了提高性能作的優化,在新環境下竟然影響性能。

關於性能的另外一個問題是不少術語的使用不一致。最明顯的例子就是「可伸縮性」(scalability),它可能有6-7種含義。下面我使用其中一些術語。

響應時間是系統完成一次外部請求處理所須要的時間。這些外部請求多是用戶交互行爲,例如按下一個按鈕,或是服務器API調用。

響應性不一樣於請求處理,它是系統響應請求的速度有多快。這個指標在許多系統裏很是重要,由於對於一些系統而言,若是其響應性太慢,用戶將難以忍受——儘管其響應時間可能不慢。若是在請求處理期間,系統一直處於等待狀態,則系統的響應性和響應時間是相同的。然而,若是可以在處理真正完成以前就給用戶一些信息代表系統已經接到請求,則響應性就會好一些。例如,在文件拷貝過程當中,爲用戶提供一個「進度條」,將會提升用戶界面的響應性,但並不會提升響應時間。

等待時間是得到系統任何形式響應的最小時間,即便應該作的工做並不存在。一般它是遠程系統中的大問題。假設咱們讓程序什麼都不作,只是調用返回便可,則若是在本機上運行程序,通常都會當即獲得響應。可是,若是在遠程計算機上運行程序,狀況就不同,每每須要數秒的時間才能獲得響應。由於從發出請求到獲得響應的數秒時間主要用於排除使信息在線路上傳輸的困難。做爲應用開發者,我常常對等待時間無能爲力。這也是爲何要儘可能避免遠程調用的緣由。

吞吐率是給定時間內可以處理多大的請求量。若是考察的是文件拷貝,則吞吐率能夠用每秒字節量來表示。對於企業應用來講,吞吐率一般用每秒事務數(tps)來度量。這種方法的一個問題是指標依賴於事務的複雜程度。對於特定系統的測試,應該選取普通的事務集合。

在這裏,性能或指吞吐率,或者指響應時間,由用戶本身決定。當經過某種優化技術後,使得系統的吞吐率提升了,可是響應時間降低了,這時就很差說系統的性能提升了,最好用更準確的術語表示。從用戶角度而言,響應性每每比響應時間更重要,所以,爲了提升響應性而損失一些響應時間或者吞吐率是值得的。

負載是關於系統當前負荷的表述,也許能夠用當前有多少用戶與系統相連來表示。負載有時也做爲其餘指標(如響應時間)的背景。所以,咱們能夠說:在10個用戶的狀況下,請求響應時間是0.5秒,在20個用戶的狀況下,請求響應時間是2秒。

負載敏感度是指響應時間隨負載變化的程度。假設:系統A在10~20個用戶的狀況下,請求響應時間都是0.5秒;系統B在10個用戶的狀況下,請求響應時間是0.2秒,在20個用戶的狀況下,請求響應時間上升到2秒。此時,系統A的負載敏感度比系統B低;咱們還能夠使用術語衰減(degradation),稱系統B衰減得比系統A快。

效率是性能除以資源。若是一個雙CPU系統的性能是30tps,另外一個系統有4個一樣的CPU,性能是40tps,則前者效率高於後者。

系統的容量是指最大有效負載或吞吐率的指標。它能夠是一個絕對最大值或性能衰減至低於一個可接受的閾值以前的臨界點。

可伸縮性度量的是向系統中增長資源(一般是硬件)對系統性能的影響。一個可伸縮性的系統容許在增長了硬件後,可以有性能上的合理提升。例如,爲了使吞吐率提升一倍,要增長多少服務器等。垂直可伸縮性或稱垂直延展,一般指提升單個服務器的性能,例如增長內存。水平可伸縮性或稱水平延展,一般指增長服務器的數目。

問題是,設計決策對全部性能指標的做用並不相同。好比,某個服務器上運行着兩個軟件系統:Swordfish的容量是20tps,而Camel的容量是40tps。哪個的性能更高?哪個的可伸縮性好?僅憑這些數據,咱們沒法回答關於可伸縮性的問題,咱們只能說Camel系統在單片機上的效率更高。假設又增長了一臺服務器後,咱們發現:Swordfish的容量是35tps,Camel的容量是50tps。儘管Camel的容量仍然大於Swordfish,可是後者在可伸縮性上卻顯得比前者更好。假設咱們繼續增長服務器數目後發現:Swordfish每增長一臺服務器提升15tps,Camel每增長一臺服務器提升10tps。在得到了這些數據後,咱們才能夠說,Swordfish的水平可伸縮性比Camel好,儘管Camel在5個服務器如下會有更好的效率。

當構建企業應用系統時,關注硬件的可伸縮性每每比關注容量或效率更重要。若是須要,可伸縮性能夠給予你得到更好性能的選擇,可伸縮性也能夠更容易實現。有時,設計人員費了九牛二虎之力才提升了少量容量,其開銷還不如多買一些硬件。換句話說,假設Camel的費用比Swordfish高,高出的部分正好能夠買幾臺服務器,那麼選擇Swordfish可能更合算,儘管你目前只須要40tps。如今人們常常抱怨軟件對硬件的依賴性愈來愈大,有時爲了運行某些軟件就不得不對硬件進行升級,就像我同樣,爲了用最新版本的Word,就必須不斷地升級筆記本電腦。可是總的來講,購買新硬件仍是比修改舊軟件來得便宜。一樣,增長更多的服務器也比增長更多的程序員來得便宜——只要你的系統有足夠的可伸縮性。

0.5 模式

模式的概念早就有了。我在這裏不想把這段歷史從新演繹一遍。只是想簡單談談我對模式和它們爲何是描述設計的重要手段的一些見解。

模式沒有統一的定義。可能最好的起點是Christopher Alexander給出的定義(這也是許多模式狂熱者的靈感來源):「每個模式描述了一個在咱們周圍不斷重複發生的問題以及該問題解決方案的核心。這樣,你就能一次又一次地使用該方案而沒必要作重複勞動」[Alexander et al.]。儘管Alexander是建築家,他談論的是建築模式,但其定義也能很好地適用於軟件業。模式的核心就是特定的解決方案,它有效並且有足夠的通用性,能解決重複出現的問題,模式的另外一種視角是把它當作一組建議,而創造模式的藝術則是將不少建議分解開來,造成相互獨立的組,在此基礎上能夠相對獨立地討論它們。

模式的關鍵點是它們源於實踐。必須觀察人們的工做過程,發現其中好的設計,並找出「這些解決方案的核心」。這並非一個簡單的過程,可是一旦發現了某個模式,他將是很是有價值的。對於我來講,價值之一是可以撰寫這樣一本參考書。你沒必要通讀本書的所有內容,也沒必要通讀全部有關於模式的書。你只須要了解到這些模式都是幹什麼的,它們解決什麼問題,它們是如何解決問題的,就足夠了。這樣,一旦碰到相似問題,就能夠從書中找出相應的模式。那時,再深刻了解相應的模式也不遲。

一旦須要使用模式,就必須知道如何將它運用於當前的問題。使用模式的關鍵之一是不能盲目使用,這也是模式工具爲何都那麼慘的緣由。我認爲模式是一種「半生不熟品」,爲了用好它,還必須在本身的項目中把剩下的那一半「火候」補上。我本人每次在使用模式時,都會東改一點西改一點。所以你會屢次看到同一解決方案,但沒有一次是徹底相同的。

每一個模式相對獨立,但又不彼此孤立。有時候它們相互影響,如影隨形。例如,若是在設計中使用了領域模型,那麼常常還會用到類表繼承。模式的邊界原本也是模糊的,我在本書中也儘可能讓它們各自獨立。若是有人說「使用工做單元」,你就能夠直接去看工做單元這個模式如何使用,而沒必要閱讀全書。

若是你是一個有經驗的企業應用設計師,也許會對大多數模式都很熟悉。但願本書不會給你帶來太大的失望。(實際上我在前言裏面已經提醒過了。)模式不是什麼新鮮概念。所以,撰寫模式書籍的做者們也不會聲稱咱們「發明」了某某模式,而是說咱們「發現」了某某模式。咱們的職責是記錄通用的解決方案,找出其核心,並把最終的模式記錄下來。對於一個高級設計師,模式的價值並不在於它給予你一些新東西,而在於它能幫助你更好地交流。若是你和你的同事都明白什麼是遠程外觀,你就能夠這樣很是簡潔地交流大量信息:「這個類是一個遠程外觀模式。」也能夠對新人說:「用數據傳輸對象模式來解決這個問題。」他們就能夠查找本書來搞清楚如何作。模式爲設計提供了一套詞彙,這也是爲何模式的名字這麼重要的緣由。

本書的大多數模式是用來解決企業應用的,基本模式一章(見第18章)則更通用一些。我把它們包含進來的緣由是:在前面的討論中,我引用了這些通用的模式。

0.5.1 模式的結構

每一個做者都必須選擇表達模式的形式。一些人採用的表達基於模式的一些經典教材如[Alexander et al.]、[Gang of Four]或[POSA]。另外一些人用他們本身的方式。我在這個問題上也斟酌了好久。一方面我不想象GOF同樣太精煉,另外一方面我還要引用他們的東西。這就造成了本書的模式結構。

第一部分是模式的名字。模式名很是重要,由於模式的目的之一就是爲設計者們交流提供一組詞彙。所以,若是我告訴你Web服務器是用前端控制器和轉換試圖構建的,而你又瞭解這些模式,那麼你對個人Web服務器的架構就會很是清楚了。

接下來的兩部分是相關的:意圖和概要。意圖用一兩句話總結模式;概要是模式的一種可視化表示,一般是(但不老是)一個UML圖。這主要是想給模式一個簡單的概況,以幫助記憶。若是你對模式已經「心知肚明」,只是不知道它的名字,那麼模式的意圖和概要這兩部分就能爲你提供足夠的信息。

接下來的部分描述了模式的動機。這可能不是該模式所能解決的惟一問題,但倒是我認爲最具表明性的問題。

運行機制」部分描述瞭解決方案。在這一部分,我會討論一些實現問題以及我遇到的變化狀況。我會盡量獨立於平臺來討論——也有一個部分是針對平臺來討論的,若是不感興趣能夠跳過這部分。爲了便於解釋,我用了一些UML圖來輔助說明。

使用動機」部分描述了模式什麼時候被使用。這部分討論是使我選擇該模式而不是其餘模式的權衡考慮。本書中不少模式均可以相互替代,例如頁面控制器和前端控制器能夠相互替代。不多有什麼模式是非它不可的。所以,每當我選擇了一種模式以後,我老是問本身「你何時不用它?」這個問題也常常驅使我選擇其餘方案。

進一步閱讀」部分給出了與該模式相關的其餘讀物。它並不完善。我只選擇我認爲有助於理解模式的參考文獻,因此我去掉了對本書內容沒有價值的任何討論,固然其中也可能會遺漏一些我不知道的模式。我也沒有提到一些我認爲可能讀者沒法找到的參考文獻,再就是一些不太穩定的Web連接。

我喜歡爲模式增長一個或幾個例子。每一個例子都很是簡單,它們是用Java語言或C#語言編寫的。我之因此選擇兩種語言,是由於它們多是目前絕大多數專業程序員都能讀懂的語言。必須注意,例子自己不是模式。當你使用模式時,不要想固然地認爲它會和例子同樣,也不要把例子當作某種形式的宏替換。我把例子編得儘可能簡單以突出其中模式相關的部分。固然,省略的部分並非不重要,只是它們通常都特定於具體環境,這也是爲何模式在使用時通常都必須作適當調整的緣由。

爲了儘可能使例子簡單可是又可以突出核心意思,我主要選擇那些簡單而又明確的例子,而不是那些來自於系統中的複雜例子。固然,在簡單和過度之間掌握平衡是不容易的,可是咱們必須記住:過度強調具體應用環境反而會增長模式的複雜性,使得模式的核心內容不易理解。

這就是爲何我在選擇例子時選取的是一些相互獨立的例子而不是相互關聯的例子的緣由。獨立的例子有助於對模式的理解。可是在如何將這些模式聯合在一塊兒使用上卻支持很少。相互關聯的例子則相反,它體現了模式間是如何相互做用的,可是對其中每一個模式的理解卻依賴於對其餘全部模式的理解。理論上,是能夠構造出既相互關聯又相互獨立的例子,但這是一項很是艱鉅的工做——至少對於我來講是這樣。所以,我選擇了相互獨立的例子。

例子中的代碼自己也主要用來加強對思想的理解。所以,在其餘一些方面考慮可能不夠——特別是錯誤處理,在這方面,我沒有花費不少筆墨,由於到目前爲止,我尚未得出錯誤處理方面的模式。在此,那些代碼純粹用來講明模式,而並非用來顯示如何對任何特定的業務問題進行建模。

正是因爲這些緣由,我沒有把這些代碼放到個人網站上供你們下載。爲了讓那些基本的思想在應用設置下有所意義,本書的每一個樣例代碼都充滿着太多的「腳手架」來簡化它們。

並非每一個模式中都包含上面所述的各個部分。若是我不能想出很好的例子或動機等內容,我就會把相應部分省略。

0.5.2 模式的侷限性

正如我在前言中所述,對於企業應用開發而言,本書介紹的模式並不全面。我對本書的要求,不在於它是否全面,而在於它是否有用。模式這個領域太大了,單憑一我的的頭腦是沒法作到面面俱到的,更不用說是一本書了。

本書中所列的模式都是我在具體領域中遇到的,但這並不代表我已經理解了每個模式以及它們之間的關係。本書的內容只是反映了我在寫書時的理解,在編寫本書的過程當中,我對相關內容的理解也不斷髮展和加深,固然,在本書發表以後,我仍然但願本人對模式的理解還可以繼續發展。對於軟件開發而言,有一點是能夠確定的,那是軟件開發永遠不會中止。

當你使用模式時請記住:它們只是開始,而不是結束。任何做者去囊括項目開發中的全部變化和技術是不可能的。我編寫本書的目的也只是做爲一個開始,但願它可以把我本身的和我所瞭解的經驗和教訓傳遞給讀者,大家能夠在此基礎上繼續努力。請你們記住:全部模式都是不完備的,大家都有責任在本身的系統中完善它們,大家也會在這個過程當中獲得樂趣。

 

第1章 分層

    在分解複雜的軟件系統時,軟件設計者用得最多的技術之一就是分層。在計算機自己的架構中,能夠看到:處處都有分層的例子:不一樣的層從包含了操做系統調用的程序設計語言,到設備驅動程序和CPU指令集,再到芯片內部的各類邏輯門。網絡互聯中, FTP層架構在TCP之上, TCP架構在IP之上, IP又架構在以太網之上。
    當用分層的觀點來考慮系統時,能夠將各個子系統想像成按照「多層蛋糕」的形式來組織,每一層都依託在其下層之上。在這種組織方式下,上層使用了下層定義的各類服務,而下層對上層一無所知。另外,每一層對本身的上層隱藏其下層的細節。所以,第4層使用第3層的服務,第3層使用第2層的服務,第4層無需知道第2層的細節。(固然,並不是全部的分層架構都這麼隔絕,但絕大多數是不透明的,或至少是幾乎不透明的。)
將系統按照層次分解有不少重要的好處:
• 在無需過多瞭解其餘層次的基礎上,能夠將某一層做爲一個有機總體來理解。例如,無需知道以太網的工做細節,你照樣能夠在TCP上構建FTP服務。
• 能夠替換某層的具體實現,只要先後提供的服務相同便可。例如, FTP服務不管是在以太網、 PPP上、仍是網絡運營商使用的任何網絡上都無需改變,並且與提供傳輸電纜的網絡
運營商無關。
• 能夠將層次間的依賴性減到最低。假設網絡運營商改變了物理傳輸系統,但只要IP層不變,FTP服務就能夠不改變。
• 分層有利於標準化工做。 TCP和IP就是關於它們各自層次如何工做的標準。
• 一旦構建好了某一層次,就能夠用它爲不少上層服務提供支持。所以, TCP/IP同時被FTP、telnet、 SSH和HTTP使用。不然,全部這些高層協議都必須編寫它們各自的底層協議。分層是一種重要的技術,但也有缺陷:
• 層次並不能封裝全部東西。有時它會爲咱們帶來級聯修改。最經典的例子就是在一個分層設計的企業應用中,若是要增長一個在用戶界面上顯示的數據域,就必須在數據庫中增長相應的字段,還必須在用戶界面和數據庫之間的每一層作相應的修改。
• 過多的層次會影響性能。在每一層,通常都會從一種表現形式轉換到另外一種。不過底層功能的封裝一般帶來比代價更大的效率提高。例如,能夠優化事務控制層,提升其餘各層的效率。
然而,分層架構中最困難的問題是決定創建哪些層次以及每一層的職責是什麼。

1.1 企業應用中層次的演化
我雖然沒有從事過早期批處理系統時期的任何工做,但我認爲當時的軟件工做人員不會太
關注層次的概念,只要編寫操做某些文件( ISAMVSAM等)格式的程序,這就是當時的應用。
它不須要層次。
20世紀90年代,隨着客戶/服務器系統的出現,分層的概念更明顯了。這樣的系統是一種兩
個層次的系統:客戶端包括用戶界面和其餘應用代碼,服務器端一般是關係型數據庫。常見的
客戶端工具如VBPowerBuilderDelphi。這些工具使得構建數據密集型應用很是容易。由於
它們的用戶界面控件一般都是SQL感知的。所以,能夠經過將控件拖拽到「設計區域」來創建
界面,而後再使用屬性表單把控件鏈接到後臺數據庫。
若是應用僅僅包括關係數據的簡單顯示和修改,那麼這種客戶/服務器系統的工做方式很是
合適。問題來自領域邏輯:如業務規則、驗證、計算等。一般,人們會把它們寫在客戶端,但
是這樣很笨拙,而且每每把領域邏輯直接嵌入到用戶界面。隨着領域邏輯的不斷複雜化,這些
代碼將愈來愈難以使用。並且,這樣作很容易產生冗餘代碼,這意味着簡單的變化都會致使要
在不少界面中尋找類似代碼。
另一種辦法是把這些領域邏輯放到數據庫端,做爲存儲過程。可是,存儲過程只提供有
限的結構化機制,這將再次致使笨拙的代碼。並且,不少人喜歡關係型數據庫的緣由之一是
SQL是一個標準,容許他們更換數據庫廠商。儘管真正更換數據庫廠商的用戶寥寥無幾,但還
是有不少人但願擁有這種選擇,而且沒有太大的附加代價。因爲存儲過程都是數據庫廠商私有
的,所以普通用戶被剝奪了這種選擇權。
在客戶/服務器方式逐漸大衆化的同時,面向對象方式開始崛起。面向對象爲領域邏輯的問
題找到了答案:轉到三層架構的系統。在這種方式下,在表現層實現用戶界面,在領域層實現
領域邏輯,在數據源層存取數據。這種方式使你能夠將複雜的領域邏輯從界面代碼中抽取出來,
單獨放到中間層,用對象加以建模和組織。
儘管有這些優點,但一開始面向對象的進展並不大。當時的實際狀況是:大多數系統並不
特別複雜,或者至少在構建之初沒有那麼複雜。所以,當系統比較簡單時,相對於三層架構的
優點,強有力的客戶/服務器工具的競爭力很是大。但客戶/服務器工具很難甚至沒法應用於三層
架構系統的配置。
我認爲真正巨大的衝擊來自Web的興起。人們突然想在Web瀏覽器上部署這些客戶/服務器
應用。然而,若是全部的領域邏輯都是寫在「胖」客戶中,則全部這些都必須在Web界面中重寫。
對於設計良好的三層系統來講,只須要增長一個新的表現層,就能夠了。另外, Java的出現使得
面嚮對象語言無所顧忌地向當時的主流技術發起衝擊。用於構建Web頁面的工具對SQL的綁定也
沒有那麼緊密了,這也使得它們比較容易適應三層結構。
當人們討論分層時,經常不容易區分layertier。這兩個詞彙常常被用做同義詞,可是不少
人仍是認爲tier意味着物理上的分離。客戶/服務器系統經常被稱爲「two-tier system」,其分離是
物理上的分離:客戶端是一臺臺式機,而服務器端是一臺服務器。我使用layer,旨在強調無需
把不一樣的層次放在不一樣的計算機上運行。獨立出來的領域邏輯層,既能夠運行在臺式計算機上,

也能夠運行在數據庫服務器上。在這種情形下,有兩個節點,可是有三個層次。若是數據庫也
在本地,還能夠在一臺筆記本電腦上運行三層軟件,固然,仍舊存在三個大相徑庭的層次。
1.2 三個基本層次
本書主要就三個基本層次的架構展開討論:表現層、領域層和數據源層(這裏的命名取自
文獻[Brown et al.])。表1-1總結了這些層次。
1-1 三個基本層次
層 次 職 責
表現層 提供服務,顯示信息(例如在WindowsHTML頁面中,處理
用戶請求(鼠標點擊,鍵盤敲擊等), HTTP請求,命令行調用,
批處理API
領域層 邏輯,系統中真正的核心
數據源層 與數據庫、消息系統、事務管理器及其餘軟件包通訊
表現邏輯處理用戶與軟件間的交互。可能簡單到只是命令行或基於文本的菜單系統,可是
當前的客戶界面每每是功能完善的胖客戶圖形界面,或者是基於HTML的瀏覽器界面(本書中的
「胖客戶」是指Windows/Swing/fat-client用戶界面,不包括HTML瀏覽器)。表現層的主要職責是
向用戶顯示信息並把從用戶那裏獲取的信息解釋成領域層或數據源層上的各類動做。
數據源邏輯主要關注與其餘系統的交互,這些系統將表明應用完成相關的任務。它們能夠
是事務監控器、其餘應用、消息系統等。對於大多數企業應用來講,最主要的數據源邏輯就是
數據庫,它的主要責任是存儲持久數據。
最後一部分就是領域邏輯,也稱爲業務邏輯。它就是應用必須作的全部領域相關工做:包
括根據輸入數據或已有數據進行計算,對從表現層輸入的數據進行驗證,以及根據從表現層接
收的命令來肯定應該調度哪些數據源邏輯。
有時,層次組織成領域層對錶現層徹底隱藏了數據源層。但更多的時候,是表現層直接對
數據存儲進行操做。雖然這樣作並不純粹,可是在實踐中每每運行良好。表現層可能解釋來自
用戶的命令,經過數據源層將相關數據從數據庫中提取出來,而後讓領域邏輯層在向用戶顯示
相關數據以前先處理這些相關數據。
一個單獨的企業應用,可能在上述的三個層次上都包含多個軟件包。若是某個應用不只要
支持用戶經過胖客戶機界面訪問,還要支持用戶經過命令行形式訪問,則它須要兩個表現層:
一個支持胖客戶機界面,另外一個支持命令行。對於不一樣的數據庫,一般也須要多個數據源組件,
特別是在與已有的軟件包通訊時。即使是領域邏輯,也有可能被分割成相互獨立的不一樣部分,
特定的數據源包只能由特定的領域包使用。
到目前爲止,咱們一直都在討論用戶。這很天然地會引出一個問題:若是驅動軟件的不是
人,狀況又怎麼樣呢?好比說驅動者多是時髦的Web Service或是一個老土但實用的批處理程
序。對於後者,用戶將是一個客戶程序。這樣,很明顯,表現層就有可能與數據源層出現某些
類似之處,由於它們都是系統與外界的接口。這就是Alistair CockburnHexagonal Architecture
模式[wiki]背後的邏輯,它將任何系統都視爲由到外部系統的接口所圍繞的一個核心。在

Hexagonal Architecture中,全部外部的東西都被視爲外部接口。所以,從這種意義上說,它是一
種對稱視圖,而不是本書中的非對稱分層視圖。
然而,我認爲這種非對稱性是有益的。由於,爲別人提供服務的接口與使用別人服務的接
口存在較大的差異,須要明確區分。這就是表現層和數據源層相對於核心的本質差異。表現層
是系統對外提供服務的外部接口,無論外面是複雜的人類仍是一個簡單的遠端程序。數據源層
是系統使用外部服務的接口。這樣區分的好處是:客戶的不一樣將改變你對服務的見解。
對每一個企業應用,儘管咱們可以區分出其中的主要的表現層、領域層和數據源層,可是具
體如何分離要取決於應用的複雜程度。 從數據庫中讀取數據並把它顯示在Web頁面上的簡單腳本,
可能所有在一個過程當中。我將仍然儘可能保持三層架構的風格,不過在這裏可能只是把每一個層的
行爲放到三個不一樣的子程序中。一旦系統再複雜一點,就能夠將三個層次的工做分解成不一樣的
類。若是複雜度繼續增長,則把類分配到不一樣的包中。個人整體建議就是根據不一樣的問題,選
擇一種適合的分離方式,可是切記必定要進行某種形式的分離—至少在子程序級別。
伴隨着分離,還有一條關於依賴性的廣泛原則:領域層和數據源層絕對不要依賴於表現層。
也就是說,在領域層和數據源層的代碼中,不要出現調用表現層代碼的狀況。這條規則將簡化
在相同的基礎上替換表現層的代價,也使得表現層的修改所帶來的連鎖反應儘量小。領域層
與數據源層的關係更復雜,其取決於數據源層的架構模式。
使用領域邏輯時,其中一個最困難的部分就是區分什麼是領域邏輯,什麼是其餘邏輯。一
種不太正規的測試辦法就是:假想向系統中增長一個徹底不一樣的新層,例如爲Web應用增長一個
命令行界面層。若是在這個過程當中,發現須要重複實現某些功能,則說明可能有一些本應該在
領域層實現的邏輯,如今在表現層實現了。相似地,你也能夠假想一下,將後臺數據庫更換成
XML文件格式,看看狀況又會如何?
舉一個例子。我所知道的一個系統有一張產品列表,其中,當月銷售量比上月銷售量大10
的產品須要用紅色顯示。爲實現這個功能,開發者在表現層邏輯中比較當月和上月的銷售量,
而後將差異大於10%的產品顯示爲紅色。
這樣作的麻煩就是將領域邏輯放到了表現層中。爲了進行適當的分離,須要在領域層中定
義一個方法,用來指示該產品的銷售量是否較上月有較大提升。該方法完成銷售量的比較,返
回一個布爾值。表現層則只須要簡單地調用一下這個布爾方法,若是返回值爲真,則用紅色突
出顯示這個產品。這樣,該過程就分解成兩部分:肯定需不須要突出顯示,選擇如何突出顯示。
固然,我擔憂這樣也許有些太教條主義了。 Alan Knight在審閱本書時評論說:他本身「很
頭大,將部分領域邏輯混入表現層究竟是滑向地獄的第一步呢,仍是隻有少數純粹主義者纔會
抱怨的小問題?」咱們之因此擔憂,正是由於這種作法二者兼備。
1.3 爲各層選擇運行環境
本書絕大多數篇幅討論的都是邏輯層次—將系統中各部分分離,以下降不一樣部分之間的
耦合程度。即便是都運行在同一臺計算機上,不一樣層次間的分離也是很是重要的。固然,系統
物理結構的不一樣會有所影響。
對於大多數信息系統來講,主要的決定就是在哪裏運行處理工做,是在客戶機上,仍是在

臺式機上,又或是在服務器上?
一般,最簡單的狀況是將全部東西都運行在服務器上。在這種狀況下,一個使用Web瀏覽器
HTML前端是一個好方法。這樣作最大的好處是全部的東西都在有限的環境內,很容易修改維
護。無需考慮將它們分發到不一樣的客戶端並維護與服務器的同步等問題。也沒必要考慮與客戶機
上其餘軟件的兼容性問題。
在客戶機上運行應用程序的好處是系統的響應性好,或者在網絡斷開的狀況下也能正常工
做。任何運行在服務器上的邏輯在響應客戶請求時,都須要一個來回的通訊開銷。若是用戶僅
僅是爲了試試系統的即時反饋,這個通訊來回也沒法避免。它還須要在網絡鏈接保持的狀態下
運行。固然,網絡的分佈可能會無所不在,可是至少在我寫本書的時候, 31000英尺高的地方還
沒有。也許在不久的未來,就會處處都有了,但有很多人須要當即工做,沒必要等待網絡鏈接。
斷接操做帶來特別的挑戰性,我不想在本書中過多討論。
有了這些約束,咱們就能夠逐層分析了。數據源層通常都是運行在服務器上。例外狀況是:
當須要斷接操做時,能夠將服務器的功能複製到一臺功能強大的客戶機上。在這種狀況下,在
離線的客戶機上對數據源的任何修改,都須要同步到服務器上。正如我前面提到的那樣,關於
這方面的討論,我想留到之後某個時候或留給另外一位做者。
在何處運行表現層主要取決於用戶界面的種類。若是運行的是一個胖客戶,則意味着表現
層運行在客戶端。若是運行的是一個Web界面,則意味着表現層運行在服務器端。固然,也有例
外—例如,客戶軟件(如Unix中的X servers)的遠程操做在臺式機上運行了一個Web服務器—當
然,這是極少數狀況。
若是要創建一個B2C系統,就沒什麼選擇了。不管是誰均可能訪問你的服務器,你也不想因
爲客戶用的是TRS-80系統,就把他拒之門外。在這種狀況下,能夠在服務器上完成全部工做,
並提供一個HTML界面給瀏覽器。這樣作的缺點就是每一個操做都必需要一個來回的通訊開銷,可
能會影響響應時間。能夠經過可下載的applet或瀏覽器腳原本緩解問題,可是它們同時會帶來瀏
覽器兼容性等其餘問題。使用的HTML越純粹,事情就會變得越簡單。
即便大家的每一臺臺式機都是由大家公司的信息系統部門手工精心搭建的,這種簡單性仍
然很是誘人。由於即便是很是簡單的胖客戶系統,也會遇到維護客戶端一致性以及避免各類軟
件不兼容等問題。
人們但願有胖客戶表現層的主要緣由是:有些任務對於用戶而言太複雜了,爲了有一個可
用的應用系統,它們的須要超出了Web GUI的能力。固然,人們已經在逐漸習慣於採用使Web
端更可用的各類方法,這些方法減小了對胖客戶方式的需求。所以,我很是贊同只要有可能就
Web表現方式,只有在必需的狀況下才使用胖客戶方式。
剩下來的就是領域邏輯了。領域邏輯能夠全都運行於服務器端,也能夠全都運行於客戶端,
也能夠一分爲二。再有,所有運行在服務器端有助於系統維護,向客戶端轉移是爲了響應時間
及斷接使用的須要。
若是你必須在客戶端運行某種邏輯,能夠考慮將全部邏輯都運行在客戶端—這樣至少保
證了相關的東西都在一塊兒。這樣,胖客戶端和Web服務器聯合部署在客戶機上,對響應性的改善
不會太大,可是它能夠做爲一種處理斷接操做的辦法。在這種狀況下,能夠經過不一樣的模塊將

領域邏輯與表現層分開,能夠使用事務腳本或領域模型。將全部的領域邏輯放在客戶端的問題
是升級和維護代價高。
將領域邏輯分割在客戶端和服務器端,應該是最差的選擇,由於這樣作沒法肯定任意一塊
邏輯到底在哪。採用這種方式的主要緣由是:只有一小部分領域邏輯須要在客戶端完成。其中
的訣竅就是將這一小部分獨立出來成爲自包含的模塊,使得它不依賴於系統的任意其餘部分。
這樣,就能夠在客戶端或服務器端運行它了。這將須要採用一些煩人的小技巧,但它的確是一
個解決問題的好辦法。
一旦選擇了處理節點,接下來就應該儘量使全部代碼保持在單一進程內完成(多是在
同一個節點上,也可能拷貝在集羣中的多個節點上)。除非不得已,不然不要把層次放在多個進
程中完成。由於那樣作不但損失性能,並且增長複雜性,由於必須增長相似下面的模式,如遠
程外觀和數據傳輸對象。
如下因素被Jens Coldewey稱爲複雜性增壓器( complexity booster):分佈、顯式多線程、範
型差別(例如對象/關係)、多平臺開發以及極限性能要求(如每秒100個事務以上)。全部這些因
素都會帶來很大的代價。固然,有時咱們沒法迴避它們,可是要切記:這裏羅列的每一項都會
爲開發和運行維護階段帶來開銷。

 

——選自:《企業應用架構模式》 [Patterns of Enterprise Application Architecture] [英] 福勒 著;王懷民,周斌 譯

相關文章
相關標籤/搜索