1. 程式人生 > >好的軟體架構設計

好的軟體架構設計

Part 1 層

    層(layer)這個概念在計算機領域是非常了不得的一個概念。計算機本身就體現了一種層的概念:系統呼叫層、裝置驅動層、作業系統層、CPU指令集。每個層都負責自己的職責。網路同樣也是層的概念,最著名的TCP/IP的七層協議。

    層到了軟體領域也一樣好用。為什麼呢?我們看看使用層技術有什麼好處:

    ● 你使用層,但是不需要去了解層的實現細節。    ● 可以使用另一種技術來改變基礎的層,而不會影響上面的層的應用。    ● 可以減少不同層之間的依賴。    ● 容易制定出層標準。    ● 底下的層可以用來建立頂上的層的多項服務。

    當然,層也有弱點:

    ● 層不可能封裝所有的功能,一旦有功能變動,勢必要波及所有的層。    ● 效率降低。

當然,層最難的一個問題還是各個層都有些什麼,以及要承擔何種責任。

典型的三層結構

    三層結構估計大家都很熟悉了。就是表示(presentation)層, 領域(domain)層, 以及基礎架構(infrastructure)層。

    表示層邏輯主要處理使用者和軟體的互動。現在最流行的莫過於視窗圖形介面(wimp)和基於html的介面了。表示層的主要職責就是為使用者提供資訊,以及把使用者的指令翻譯。傳送給業務層和基礎架構層。

    基礎架構層邏輯包括處理和其他系統的通訊,代表系統執行任務。例如資料庫系統互動,和其他應用系統的互動等。大多數的資訊系統,這個層的最大的邏輯就是儲存持久資料。

    還有一個就是領域層邏輯,有時也被叫做業務邏輯。它包括輸入和儲存資料的計算。驗證表示層來的資料,根據表示層的指令指派一個基礎架構層邏輯。

    領域邏輯中,人們總是搞不清楚什麼事領域邏輯,什麼是其它邏輯。例如,一個銷售系統中有這樣一個邏輯:如果本月銷售量比上個月增長10%,就要用紅色標記。要實現這個功能,你可能會把邏輯放在表示層中,比較兩個月的數字,如果超出10%,就標記為紅色。

    這樣做,你就把領域邏輯放到了表示層中了。要分離這兩個層,你應該現在領域層中提供一個方法,用來比較銷售數字的增長。這個方法比較兩個月的數字,並返回boolean型別。表示層則簡單的呼叫該方法,如果返回true,則標記為紅色。

例子

    層技術不存在說永恆的技巧。如何使用都要看具體的情況才能夠決定,下面我就列出了三個例子:

    例子1:一個電子商務系統。要求能夠同時處理大量使用者的請求,使用者的範圍遍及全球,而且數字還在不斷增長。但是領域邏輯很簡單,無非是訂單的處理,以及和庫存系統的連線部分。這就要求我們1、表示層要友好,能夠適應最廣泛的使用者,因此採用html技術;2、支援分散式的處理,以勝任同時幾千的訪問;3、考慮未來的升級。

    例子2:一個租借系統。系統的使用者少的多,但是領域邏輯很複雜。這就要求我們製作一個領域邏輯非常複雜的系統,另外,還要給他們的使用者提供一個方便的輸入介面。這樣,wimp是一個不錯的選擇。

    例子3:簡單的系統。非常簡單,使用者少、邏輯少。但是也不是沒有問題,簡單意味著要快速交付,並且還要充分考慮日後的升級。因為需求在不斷的增加之中。

何時分層

    這樣的三個例子,就要求我們不能夠一概而論的解決問題,而是應該針對問題的具體情況制定具體的解決方法。這三個例子比較典型。

    第二個例子中,可能需要嚴格的分成三個層次,而且可能還要加上另外的中介(mediating)層。例3則不需要,如果你要做的僅是檢視資料,那僅需要幾個server頁面來放置所有的邏輯就可以了。

    我一般會把表示層和領域層/基礎架構層分開。除非領域層/基礎架構層非常的簡單,而我又可以使用工具來輕易的繫結這些層。這種兩層架構的最好的例子就是在VB、PB的環境中,很容易就可以構建出一個基於SQL資料庫的windows介面的系統。這樣的表示層和基礎架構層非常的一致,但是一旦驗證和計算變得複雜起來,這種方式就存在先天缺陷了。

    很多時候,領域層和基礎架構層看起來非常類似,這時候,其實是可以把它們放在一起的。可是,當領域層的業務邏輯和基礎架構層的組織方式開始不同的時候,你就需要分開二者。

更多的層模式

    三層的架構是最為通用的,尤其是對IS系統。其它的架構也有,但是並不適用於任何情況。

    第一種是Brown model [Brown et al]。它有五個層:表示層(Presentation),控制/中介層(Controller/Mediator),領域層(Domain), 資料對映層(Data Mapping), 和資料來源層(Data Source)。它其實就是在三層架構種增加了兩個中間層。控制/中介層位於表示層和領域層之間,資料對映層位於領域層和基礎架構層之間。

    表示層和領域層的中介層,我們通常稱之為表示-領域中介層,是一個常用的分層方法,通常針對一些非可視的控制元件。例如為特定的表示層組織資訊格式,在不同的視窗間導航,處理交易邊界,提供Server的facade介面(具體實現原理見設計模式)。最大的危險就是,一些領域邏輯被放到這個層裡,影響到其它的表示層。

    我常常發現把行為分配給表示層是有好處的。這可以簡化問題。但表示層模型會比較複雜,所以,把這些行為放到非視覺化的物件中,並提取出一個表示-領域中介層還是值得的。

    Brown ISA    表示層 表示層   控制/中介層 表示-領域中介層    領域層 領域層    資料對映層 資料庫互動模式中的Database Mapper    資料來源層 基礎架構層

    領域層和基礎架構層之間的中介層屬於本書中提到的Database Mapper模式,是三種領域層到資料連線的辦法之一。和表示-領域中介層一眼,有時候有用,但不是所有時候都有用。

    還有一個好的分層架構是J2EE的架構,這方面的討論可以見『J2EE核心模式』一書。他的分層是客戶層(Client),表示層(Presentation),業務層(Business ),整合層(Integration),資源層(Resource)。差別如下圖:

    J2EE核心 ISA    客戶層 執行在客戶機上的表示層   表示層 執行在伺服器上的表示層    業務層 領域層    整合層 基礎架構層    資源層 基礎架構層通訊的外部資料

    微軟的DNA架構定義了三個層:表示層(presentation),業務層(business),和資料儲存層(data access),這和我的架構相似,但是在資料的傳遞方式上還有很大的不同。在微軟的DNA中,各層的操作都基於資料儲存層傳出的SQL查詢結果集。這樣的話,實際上是增加了表示層和業務層同資料儲存層之間的耦合度。

    DNA的記錄集在層之間的動作類似於Data Transfer Object。    

Part 2 組織領域邏輯

    要組織基於層的系統,首要的是如何組織領域邏輯。領域邏輯的組織有好幾種模式。但其中最重要的莫過於兩種方法:Transation Script和Domain Model。選定了其中的一種,其它的都容易決定。不過,這兩者之間並沒有一條明顯的分界線。所以如何選取也是門大學問。一般來說,我們認為領域邏輯比較複雜的系統可以採用Domain Model。

    Transation Script就是對錶示層使用者輸入的處理程式。包括驗證和計算,儲存,呼叫其它系統的操作,把資料回傳給表示層。使用者的一個動作表示一個程式,這個程式可以是script,也可以是transation,也可以是幾個子程式。在例子1中,檢驗,在購物車中增加一本書,顯示遞送狀態,都可以是一個Transation Script。

    Domain Model是要建立對應領域名詞的模型,例如例1中的書、購物車等。檢驗、計算等處理都放到領域模型中。

    Transation Script屬於結構性思維,Domain Model屬於OO思維。Domain Model比較難使用,一旦習慣,你能夠組織更復雜的邏輯,你的思想會更OO。到時候,即使是小的系統,你也會自然的使用Domain Model了。

    但如何抉擇呢?如果邏輯複雜,那肯定用Domain Model:如果只需要存取資料庫,那Transation Script會好一些。但是需求是在不斷進化的,你很難保證以後的需求還會如此簡單。如果你的團隊不善於使用Domain Model,那你需要權衡一下投入產出比。另外,即使是Transation Script,也可以做到把邏輯和基礎架構分開,你可以使用Gateway。

    對例2,毫無疑問要使用Domain Model。對例1就需要權衡了。而對於例3,你很難說它將來會不會像例2那樣,你現在可以使用Transation Script,但未來你可能要使用Domain Model。所以說,架構的決策是至關緊要的。

    除了這兩種模式,還有其它中庸的模式。Use Case Controller就是處於兩者之間。只有和單個的用例相關的業務邏輯才放到物件中。所以大致上他們還是在使用Transation Script,而Domain Model只是Database Gateway的一組集合而已。我不太用這種模式。

    Table Module是另一箇中庸模式。很多的GUI環境依託於SQL查詢的返回結果。你可以建立記憶體中的物件,來把GUI和資料庫分開來。為每個表寫一個模組,因此每一行都需要關鍵字變數來識別每一個例項。

    Table Module適用於很多的元件構建於一個通用關係型資料庫之上,而且領域邏輯不太複雜的情況。Microsoft COM 環境,以及它的帶ADO.NET的.NET環境都適合使用這種模式。而對於Java,就不太適用了。

    領域邏輯的一個問題是領域物件非常的臃腫。因為物件的行為太多了,類也就太大了。它必須是一個超集。這就要考慮哪些行為是通用的,哪些不是,可以由其它的類來處理,可能是Use Case Controller,也可能是表示層。

    還有一個問題,複製。他會導致複雜和不一致。這比臃腫的危害更大。所以,寧可臃腫,也不要複製。等到臃腫為害時再處理它吧。

選擇一個地方執行領域邏輯

    我們的精力集中在邏輯層上。領域邏輯要麼執行在Client上,要麼執行在Server上。

    比較簡單的做法是全部集中在Server上。這樣你需要使用html的前端以及web server。這樣做的好處是升級和維護都非常的簡單,你也不用考慮桌面平臺和Server的同步問題,也不用考慮桌面平臺的其它軟體的相容問題。

    執行在Client適合於要求快速反應和沒有聯網的情況。在Server端的邏輯,使用者的一個再小的請求,也需要資訊從Client到Server繞一圈。反應的速度必然慢。再說,網路的覆蓋程度也不是說達到了100%。

    對於各個層來說,又是怎麼樣的呢?

    基礎架構層:一般都是在Server啦,不過有時候也會把資料複製到合適的高效能桌面機,但這是就要考慮同步的問題了。

    表示層在何處執行取決於使用者介面的設計。一個Windows介面只能在Client執行。而一個Web介面就是在Server執行。也有特別的例子,在桌面機上執行web server的,例如X Server。但這種情況少的多。

    在例1中,沒有更多的選擇了,只能選在Server端。因此你的每一個bit都會繞一個大圈子。為了提高效率,儘量使用一些純html指令碼。

    人們選用Windows介面的原因主要就是需要執行一些非常複雜的任務,需要一個合適的應用程式,而web GUI則無法勝任。這就是例2的做法。不過,人們應該會漸漸適應web GUI,而web GUI的功能也會越來越強大。

    剩下的是領域邏輯。你可以全部放在Server,也可以全部放在Client,或是兩邊都放。

    如果是在Client端,你可以考慮全部邏輯都放在Client端,這樣至少保證所有的邏輯都在一個地方。而把web server移至Client,是可以解決沒有聯網的問題,但對反應時間不會有多大的幫助。你還是可以把邏輯和表示層分離開來。當然,你需要額外的升級和維護的工作。

    在Client和Server端都具有邏輯並不是一個好的處理辦法。但是對於那些僅有一些領域邏輯的情況是適用的。有一個小竅門,把那些和系統的其它部分沒有聯絡的邏輯封裝起來。

領域邏輯的介面

    你的Server上有一些領域邏輯,要和Client通訊,你應該有什麼樣的介面呢?要麼是一個http介面,要麼是一個OO介面。

    http介面適用於web browser,就是說你要選擇一個html的表示層。最近的新技術就是web service,通過基於http、特別是XML進行通訊。XML有幾個好處:通訊量大,結構好,僅需一次的迴路。這樣遠端呼叫的的開銷就小了。同時,XML還是一個標準,支援平臺異構。XML又是基於文字的,能夠通過防火牆。

    雖然XML有那麼多的好處,不過一個OO的介面還是有它的價值的。hhtp的介面不明顯,不容易看清楚資料是如何處理的。而OO的介面的方法帶有變數和名字,容易看出處理的過程。當然,它無法通過防火牆,但可以提供安全和事務之類的控制。

    最好的還是取二者所長。OO介面在下,http介面在上。但這樣做就會使得實現機制非常的複雜。

Part 3 組織web Server

    很多使用html方式的人,並不能真正理解這種方式的優點。我們有各種各樣好用的工具,但是卻搞到讓程式難以維護。

    在web server上組織程式的方式大致可以分為兩種:指令碼和server page。

    指令碼方式就是一個程式,用函式和方法來處理http呼叫。例如CGI指令碼和java servlet。它和普通的程式並沒有什麼兩樣。它從web頁面上獲得html string形態的資料,有時候還要做一些表示式匹配,這正是perl能夠成為CGI指令碼的常用語言的原因。而java servelet則是把這種分析留給程式設計師,但它允許程式設計師通過關鍵字介面來訪問資訊,這樣就會少一些表示式的判斷。這種格式的web server輸出是另一種html string,稱為response,可以通過流資料來操作。

    糟糕的是流資料是非常麻煩的,因此就導致了server page的產生,例如PHP,ASP,JSP。

    server page的方式適合迴應(response)的處理比較簡單的情況。例如“顯示歌曲的明細”,但是你的決策取決於輸入的時候,就會比較雜亂。例如“通俗和搖滾的顯示格式不同”。

    腳步擅長於處理使用者互動,server page擅長於處理格式化迴應資訊。所以很自然的就會採用指令碼處理請求的互動,使用server page處理迴應的格式化。這其實就是著名的MVC(Model View Controller)模式中的view/controller的處理。

web server端的MVC工作流程示意圖

    應用Model View Controller模式首要的一點就是模型要和web服務完全分離開來。使用Transaction Script或Domain Model模式來封裝處理流程。

    接下來,我們就把剩餘的模式歸入兩類模式中:屬於Controller的模式,以及屬於View的模式。

View模式

    View這邊有三種模式:Transform View,Template View和Two Step View。Transform View和Template View的處理只有一步,將領域資料轉換為html。Two Step View要經過兩步的處理,第一步把領域資料轉換為邏輯表示形式,第二步把邏輯表示轉換為html。

    兩步處理的好處是可以將邏輯集中於一處,如果只有一步,變化發生時,你就需要修改每一個螢幕。但這需要你有一個很好的邏輯螢幕結構。如果一個web應用有很多的前端使用者時,兩步處理就特別的好用。例如航空訂票系統。使用不同的第二步處理,就可以獲得不同的邏輯螢幕。

    使用單步方法有兩個可選的模式:Template View,Transform View。Template View其時就是把程式碼嵌入到html頁面中,就像現在的server page技術,如ASP,PHP,JSP。這種模式靈活,強大,但顯得雜亂無章。如果你能夠把邏輯程式邏輯在頁面結構之外進行很好的組織,這種模式還是有它的優點的。

    Transform View使用翻譯方式。例如XSLT。如果你的領域資料是用XML處理的,那這種模式就特別的好用。

Controller模式

    Controller有兩種模式。一般我們會根據動作來決定一項控制。動作可能是一個按鈕或連結。所這種模式就是Action Controller模式。

    Front Controller更進一步,它把http請求的處理和處理邏輯分離開來。一般是隻有一個web handle來處理所有的請求。你的所有的http請求的處理都由一個物件來負責。你改變動作結構的影響就會降到最小