前言
N層的應(yīng)用軟件系統(tǒng),由于其眾多的優(yōu)點,已經(jīng)成為典型的軟件系統(tǒng)架構(gòu),也已經(jīng)為廣大開發(fā)人員所熟知。在一個典型的三層應(yīng)用軟件系統(tǒng)中,應(yīng)用系統(tǒng)通常被劃分成以下三個層次:數(shù)據(jù)庫層、應(yīng)用服務(wù)層和用戶界面層。如下圖所示:
其中,應(yīng)用服務(wù)層集中了系統(tǒng)的業(yè)務(wù)邏輯的處理,因此,可以說是應(yīng)用軟件系統(tǒng)中的核心部分。軟件系統(tǒng)的健壯性、靈活性、可重用性、可升級性和可維護(hù)性,在很大程度上取決于應(yīng)用服務(wù)層的設(shè)計。因此,如何構(gòu)建一個良好架構(gòu)的應(yīng)用服務(wù)層,是應(yīng)用軟件開發(fā)者需要著重解決的問題。
為了使應(yīng)用服務(wù)層的設(shè)計達(dá)到最好的效果,我們通常還需要對應(yīng)用服務(wù)層作進(jìn)一步的職能分析和層次細(xì)分。很多開發(fā)者在構(gòu)建應(yīng)用服務(wù)層的時候,把數(shù)據(jù)庫操縱、業(yè)務(wù)邏輯處理甚至界面顯示夾雜在一起,或者,把業(yè)務(wù)邏輯處理等同于數(shù)據(jù)庫操縱,等等,這些,都是有缺陷的做法。本文,就在這個方面進(jìn)行設(shè)計時可采用的方案進(jìn)行一些探討。
為了使討論更具有針對性,本文會討論一些比較流行的系統(tǒng)架構(gòu),例如J2EE架構(gòu),以及JDO。在微軟的.Net平臺上,將以Websharp中間件為例。Websharp中間件是筆者開發(fā)的一個構(gòu)建在微軟.Net平臺之上的一個中間件系統(tǒng),也是實現(xiàn)文章所述的系統(tǒng)架構(gòu)的支撐系統(tǒng)。選用這些架構(gòu)做例子,也是因為.Net出現(xiàn)的時間比較短,目前在這個平臺上沒有成熟統(tǒng)一的架構(gòu),而J2EE是目前最成熟的構(gòu)建企業(yè)應(yīng)用的平臺。
自本人的《利用.Net框架開發(fā)應(yīng)用系統(tǒng)》和《實戰(zhàn)揭秘:開發(fā).Net平臺應(yīng)用系統(tǒng)框架》兩篇文章發(fā)表以來,收到很多反饋和來信,提出了很多問題。因為時間的關(guān)系,不能一一回復(fù),因此,也借本文給大家一些解答。需要說明的是,原來的Jobsinfo現(xiàn)在已經(jīng)做了升級,名稱變更為Websharp。
設(shè)計的原則和評判標(biāo)準(zhǔn)
同軟件工程的原則一樣,應(yīng)用服務(wù)層的設(shè)計,必須遵循的最重要的原則就是高內(nèi)聚和低耦合。軟件分層的本來目的,就是提高軟件的可維護(hù)性和可重用性,而高內(nèi)聚和低耦合正是達(dá)成這一目標(biāo)必須遵循的原則。盡量降低系統(tǒng)各個部分之間的耦合度,是應(yīng)用服務(wù)層設(shè)計中需要重點考慮的問題。
內(nèi)聚和耦合,包含了橫向和縱向的關(guān)系。功能內(nèi)聚和數(shù)據(jù)耦合,是我們需要達(dá)成的目標(biāo)。橫向的內(nèi)聚和耦合,通常體現(xiàn)在系統(tǒng)的各個模塊、類之間的關(guān)系,而縱向的耦合,體現(xiàn)在系統(tǒng)的各個層次之間的關(guān)系。
系統(tǒng)的框架,通常包含了一系列規(guī)范、約定和支撐類庫、服務(wù)。
對于如何判斷一個軟件的系統(tǒng)框架的優(yōu)劣,筆者認(rèn)為,可以從以下幾個方面來評判:
◆ 系統(tǒng)的內(nèi)聚和耦合度
這是保證一個系統(tǒng)的架構(gòu)是否符合軟件工程原則的首要標(biāo)準(zhǔn)。
◆ 層次的清晰和簡潔性
系統(tǒng)每個部分完成功能和目標(biāo)必須是明確的,同樣的功能,應(yīng)該只在一個地方實現(xiàn)。如果某個功能可以在系統(tǒng)不同的地方實現(xiàn),那么,將會給后來的開發(fā)和維護(hù)帶來問題。
系統(tǒng)應(yīng)該簡單明了,過于復(fù)雜的系統(tǒng)架構(gòu),會帶來不必要的成本和維護(hù)難度。在盡可能的情況下,一個部分應(yīng)該完成一個單獨并且完整的功能。
◆ 易于實現(xiàn)性
如果系統(tǒng)架構(gòu)的實現(xiàn)非常困難,甚至超出團(tuán)隊現(xiàn)有的技術(shù)能力,那么,團(tuán)隊不得不花很多的精力用于架構(gòu)的開發(fā),這對于整個項目來說,可能會得不償失。簡單就是美。
◆ 可升級和可擴充性
一個系統(tǒng)框架,受設(shè)計時技術(shù)條件的限制,或者設(shè)計者本人對系統(tǒng)認(rèn)識的局限,可能不會考慮到今后所有的變化。但是,系統(tǒng)必須為將來可能的變化做好準(zhǔn)備,能夠在今后,在目前已有的基礎(chǔ)上進(jìn)行演進(jìn),但不會影響原有的應(yīng)用。接口技術(shù),是在這個方面普遍應(yīng)用的技巧。
◆ 是否有利于團(tuán)隊合作開發(fā)
一個好的系統(tǒng)架構(gòu),不僅僅只是從技術(shù)的角度來看,而且,它還應(yīng)該適用于團(tuán)隊開發(fā)模型,可以方便一個開發(fā)團(tuán)隊中各個不同角色的互相協(xié)作。例如,將Web頁面和業(yè)務(wù)邏輯組件分開,可是使頁面設(shè)計人員和程序員的工作分開來同步進(jìn)行而不會互相影響。
◆ 性能
性能對于軟件系統(tǒng)來說是很重要的,但是,有的時候,為了能讓系統(tǒng)得到更大的靈活性,可能不得不在性能和其他方面取得平衡。另外一個方面,由于硬件技術(shù)的飛速發(fā)展和價格的下降,性能的問題往往可以通過使用使用更好的硬件來獲得提升。
應(yīng)用服務(wù)層的內(nèi)容
應(yīng)用服務(wù)層,通常也被稱為業(yè)務(wù)邏輯層,因為這一層,是應(yīng)用軟件系統(tǒng)業(yè)務(wù)邏輯處理集中的部分。然而,我將這一層稱為應(yīng)用服務(wù)層,而不稱業(yè)務(wù)邏輯層,因為,這一層需要處理的不僅僅是業(yè)務(wù)邏輯,還包含了其他方面的內(nèi)容。
從完整的角度來說,應(yīng)用服務(wù)層需要處理以下內(nèi)容:
◆ 數(shù)據(jù)的表示方式
數(shù)據(jù),是軟件處理的對象。從某種程度上來說,"軟件,就是數(shù)據(jù)結(jié)構(gòu)加算法"的說法,是有一定意義的。在面向?qū)ο蟮南到y(tǒng)中,數(shù)據(jù)是用類來表示的,代表了現(xiàn)實世界實體對象在軟件系統(tǒng)中的抽象?紤]所謂的MVC模式,這個部分的類屬于M--實體類的范疇。由于應(yīng)用軟件通常會使用數(shù)據(jù)庫,數(shù)據(jù)庫中的數(shù)據(jù),可以看成是對象的持久化保存。由于數(shù)據(jù)庫一般是關(guān)系型的,因此,這個部分,還需要考慮類(對象)同關(guān)系型數(shù)據(jù)的映射,即通常所說的O-R MAP問題。
◆ 數(shù)據(jù)的存取方式
如同上述所說,軟件系統(tǒng)處理的實體對象數(shù)據(jù)需要持久化保存數(shù)據(jù)庫中,因此,我們必須處理系統(tǒng)同數(shù)據(jù)庫的交互,以及數(shù)據(jù)的存取和轉(zhuǎn)換方式的問題。
◆ 業(yè)務(wù)邏輯的組織方式
在面向?qū)ο蟮南到y(tǒng)中,業(yè)務(wù)邏輯表現(xiàn)為對象之間的交互。有了上述的實體對象,以及對象的保存策略,就可以將這些對象組合起來,編寫我們的業(yè)務(wù)邏輯處理程序。在業(yè)務(wù)邏輯的處理中,必須保證處理的正確性和完整性,這將會涉及到事務(wù)處理。通常,我們也會把業(yè)務(wù)邏輯封裝成組件的形式,以得到最大的可重用性。
◆ 業(yè)務(wù)服務(wù)的提供方式
在我們完成系統(tǒng)的功能后,如何向客戶提供服務(wù),是我們需要考慮的問題。這里的客戶,不僅僅是指軟件的使用者,也包括調(diào)用的界面、其他程序等。例如,在一個基于Web的ASP.Net或JSP系統(tǒng)中,業(yè)務(wù)邏輯功能的客戶便是這些ASP.Net頁面或JSP頁面。業(yè)務(wù)邏輯組件應(yīng)該通過什么方式,直接的,或間接的,向這些客戶提供服務(wù),是這一層需要完成的任務(wù)。
◆ 層的部署和層間交互
對于一個多層的應(yīng)用軟件系統(tǒng)來說,尤其是大型的應(yīng)用軟件系統(tǒng),通常需要把不同的部分部署在不同的邏輯或物理設(shè)備上。特別是一些基于Web的應(yīng)用軟件系統(tǒng),其部署工作將涉及到Web服務(wù)器、組件服務(wù)器、數(shù)據(jù)庫服務(wù)器等不同的服務(wù)設(shè)備。在進(jìn)行應(yīng)用軟件架構(gòu)的設(shè)計的時候,必須考慮各種不同的部署方案。
綜上所述,一個完整的基于Web的應(yīng)用軟件系統(tǒng),其架構(gòu)可以用下圖來表示(Websharp推薦的應(yīng)用軟件系統(tǒng)架構(gòu)):
對于以上各個方面來說,每個問題都可以有很多種策略和方案,但是,在一個系統(tǒng)中,應(yīng)該盡可能的統(tǒng)一這些策略和方案。也就是說,在一個系統(tǒng),或者一個項目中,應(yīng)該統(tǒng)一每個解決每個問題所采用的方法。軟件的開發(fā)方法是靈活的,可以用不同的方法解決相同的問題,這會誘使開發(fā)人員采用他們認(rèn)為能夠表現(xiàn)自己的方法,但是,從整個系統(tǒng)來看,這將會是災(zāi)難性的。我們應(yīng)該盡可能統(tǒng)一,就是,采用統(tǒng)一的數(shù)據(jù)表示方式、統(tǒng)一的數(shù)據(jù)存取方式、統(tǒng)一的業(yè)務(wù)邏輯處理方式等。
下面,將就這些部分的設(shè)計策略和可用方案進(jìn)行一些比較詳細(xì)的論述。
數(shù)據(jù)實體的表示
應(yīng)用軟件系統(tǒng),從本質(zhì)上來說,是計算機對現(xiàn)實世界的模擬,F(xiàn)實世界中的實體對象,在軟件系統(tǒng)中,表現(xiàn)為需要處理的數(shù)據(jù)。在面向?qū)ο蟮南到y(tǒng)中,這是通過"類"和"對象"來表示的。
參考著名的"MVC"模式,類可以分成實體類(M)、控制類(C)、和邊界類(V),分別代表了實體對象、控制和界面顯示。系統(tǒng)中需要處理的數(shù)據(jù),在面向?qū)ο蟮南到y(tǒng)中,屬于實體類部分。
在考慮數(shù)據(jù)實體層的設(shè)計策略的時候,需要把握以下要點:
◆ 一致的數(shù)據(jù)表示方式。在一個系統(tǒng)中,數(shù)據(jù)的表示方式必須盡可能統(tǒng)一,同時,在處理單個數(shù)據(jù)和多個數(shù)據(jù)的時候,處理方式盡可能一致。
◆ 因為數(shù)據(jù)通常是需要存儲到數(shù)據(jù)庫中,因此,良好的映射方法是必需的。
◆ 處理好對象的粒度,即所謂的粗粒度對象、細(xì)粒度對象。
一般例子
考慮一個現(xiàn)實的例子,一個倉庫中的產(chǎn)品(Product),在系統(tǒng)中可以使用如下定義:
public class Product{public string Name; //名稱 public decimal Price;//價格 public int Count;//數(shù)量 } 可以按照如下方法使用Product類: Product p=new Product(); //……處理Product
這是一個包含了三個屬性的Product類的定義。為了便于說明,在這里,我們盡量將問題簡化了。
又例如,一張入庫單可以使用如下定義:
public class Form{public string ID; //入庫單編號 public DateTime AddTime; //入庫時間 public FormDetail[] FormDetails; //入庫單明細(xì) } public class FormDetail { public Product InProduct; //入庫產(chǎn)品 public int Count; //入庫數(shù)量 }
對于處理單個對象,通常采用上述的方法,但是,當(dāng)我們需要處理相同類的一組對象,也就是處理一個對象集合的時候,就會有一些小小的麻煩。
如前所述,我們希望在處理單個對象和對象集合的時候,處理的方式盡量統(tǒng)一,這對于軟件開發(fā)的意義是很大的。常用的處理對象集合的方法有:
◆數(shù)組表示的方法
例如,上面的例子中當(dāng)一張入庫單包含多條入庫單明細(xì)的時候采用的方法。為了靈活性,也可以使用容器來,如Java中的Vector或C#的ArrayList(C#)。只是,在處理對象的時候,需要一個類型轉(zhuǎn)換的操作。這個問題,在支持泛型的語言中不會存在,如使用C++的標(biāo)準(zhǔn)庫的容器類。
◆ObjectCollection方法。這個方法同上面的方法類似,不同之處在于,為每個實體類設(shè)計一個Collection類。例如,可以為FormDetail設(shè)計一個FormDetailsCollection類(C#):
public class FormDetailsCollection: ArrayList { public void Add(FormDetail detail) { base.Add(detail); } public new FormDetail this[int nIndex] { get{ return (FormDetail)base[nIndex]; } } }
這么做的好處在于,在操作集合中的對象時,不必進(jìn)行類型轉(zhuǎn)換的操作。
◆數(shù)據(jù)集的表示方法。
采用這種方法,通常是直接把從數(shù)據(jù)庫查詢中獲取的數(shù)據(jù)集(Recordset)作為數(shù)據(jù)處理對象。這種方法在ASP應(yīng)用程序中是非常常見的做法。這種做法簡單,初學(xué)者很容易掌握,但是弊病也很多。
EJB的方法
在J2EE體系中,對實體對象的處理的典型方法是Entity Bean。J2EE中使用Entity Bean來表示數(shù)據(jù),以及封裝數(shù)據(jù)的持久化儲存(同數(shù)據(jù)庫的交互)。由于Entity Bean比較消耗資源,而且采用的是遠(yuǎn)程調(diào)用的方式來訪問,因此,在需要傳遞大量數(shù)據(jù),或者在不同的層次之間傳遞數(shù)據(jù)的時候,往往還會采用一些諸如"值對象"(Value Object)的設(shè)計模式來提升性能。關(guān)于J2EE中的設(shè)計模式的更多內(nèi)容,讀者可以參考《J2EE核心模式》一書。
JDO的方法
相對于J2EE這個昂貴的方法來說,JDO提供了一個相對"輕量級"的方案。在JDO中,你可以采用一般的做法,編寫實體類,然后,通過一些強化器對這些類進(jìn)行強化,以使其符合JDO的規(guī)范,最后,你可以通過PersistenceManager來實現(xiàn)對象的持久化儲存。
無論是EJB還是JDO,在同數(shù)據(jù)庫進(jìn)行映射的時候,都選用了XML配置文件的方式。這是一種靈活的方式。由于XML強大的表達(dá)能力,我們可以很好的用它來描述代碼中的實體類和數(shù)據(jù)庫之間的映射關(guān)系,并且,不用在代碼中進(jìn)行硬編碼,這樣,在情況發(fā)生變化的時候,有可能只需要修改配置文件,而不用去修改程序的源代碼。關(guān)于EJB和JDO的配置文件的更多的信息,各位可以參考相關(guān)的文檔,這里不再贅述了。
然而,使用XML配置文件的方式并不是唯一的方法,在微軟提供的一些案例中,如Duwamish示例,就沒有采用這種方式。至于開發(fā)人員在開發(fā)過程中具體采用哪種方式,是需要根據(jù)具體情況進(jìn)行權(quán)衡和取舍的。
Websharp的方法
Websharp在數(shù)據(jù)的表現(xiàn)上,充分利用了.Net Framework類庫中DataSet的功能,設(shè)計了一個EntityData類。這個類繼承了DataSet,并增加了一些屬性和方法。同樣的,同數(shù)據(jù)庫的映射關(guān)系,也是采用XML配置文件的方式。
在實際的應(yīng)用中,要獲取一個實體對象,可以通過如下方式取得:
EntityData Customer=EntityDataManager. GetEmptyEntity("Customer");
然后,可以通過如下方式來訪問這個對象的屬性:
string CustomerID=Customer["CustomerID"]
可以看到,這種方式同傳統(tǒng)的方式有點不同。在這種方式下,數(shù)據(jù)的表現(xiàn)形式只有一個,那就是EntityData。其好處是明顯的,不用為每個實體都單獨編寫一個類,能夠大大減少代碼的編寫量。其缺點也很明顯,那就是不能利用編譯器類型檢測的功能,如果在調(diào)用對象的屬性的時候,寫錯了屬性的名稱,就可能出錯,但是,這個問題可以通過工具來解決。
關(guān)于這個方面更加詳細(xì)的信息,可以參見拙文:
《利用.Net框架開發(fā)應(yīng)用系統(tǒng)》
《實戰(zhàn)揭秘:開發(fā).Net平臺應(yīng)用系統(tǒng)框架》
數(shù)據(jù)的存取方式
數(shù)據(jù)存取的目的,是持久化保存對象,以備后來的使用,如查詢、修改、統(tǒng)計分析等。存取的對象,可以是數(shù)據(jù)庫、普通文件、XML甚至其他任何方式,只要保證數(shù)據(jù)能夠長久保存,并且,不會受斷電、系統(tǒng)重起等因素的影響。在這個部分,最理想的狀況,自然是能夠支持除了數(shù)據(jù)庫以外的各種類型的存取方式,或者,至少留有接口,能夠比較方便的擴充。
因為數(shù)據(jù)庫是最常用,也是最有效的數(shù)據(jù)存儲方法,因此,支持?jǐn)?shù)據(jù)庫存儲是最首先必須支持的。在不同的平臺下,有不同的數(shù)據(jù)庫訪問的手段。例如,在Java平臺下,有JDBC,在Windows平臺下,可以使用ADO、ADO.Net等。但是,這些手段還比較接近底層,在實際操縱數(shù)據(jù)庫的時候,需要編寫大量的代碼,并且,我們還需要通過手工的方式來完成將程序中的面向?qū)ο蟮臄?shù)據(jù)存儲到關(guān)系型數(shù)據(jù)庫的工作。這么做,自然編程的效率不高,并且非常容易出錯。但是,不可否認(rèn),這也是一種可以選用的方式。
從另外一個方面來看,由于我們前面已經(jīng)解決了數(shù)據(jù)的映射問題,因此,在數(shù)據(jù)的存取方面是非常有規(guī)律的,我們完全可以讓這個工作通過框架來執(zhí)行。這樣,我們一方面可以簡化很多同數(shù)據(jù)庫交互方面的代碼編寫工作量,能夠減少出現(xiàn)Bug的幾率,另一方面,由于框架封裝了不同數(shù)據(jù)庫之間的差異,使得我們在編寫程序的時候,不用考慮不同數(shù)據(jù)庫之間的差異,而將這個工作交給框架去做,實現(xiàn)軟件的后臺數(shù)據(jù)庫無關(guān)性。
在這個部分,以下兩個部分的類會顯得特別重要:
◆對象--關(guān)系映射的分析類,能夠通過既定的方案完成對象--關(guān)系的映射,確定數(shù)據(jù)存取方案
◆數(shù)據(jù)庫操縱類:根據(jù)映射關(guān)系,將數(shù)據(jù)準(zhǔn)確的存儲到數(shù)據(jù)庫中,并且封裝不同數(shù)據(jù)庫之間的差異。
這個部分的操作過程,可以用圖大概的表示如下:
在J2EE中,這個部分比較典型的就是EntityBean中的CMP。由于在BMP中,同數(shù)據(jù)庫的交互部分需要通過手工編寫代碼的方式來實現(xiàn),因此,很難享受到容器帶來的便利,只是由于EJB2.0以前的標(biāo)準(zhǔn),CMP的功能,包括映射能力、實體關(guān)系模式等方面的功能比較弱,所以,在很多時候,我們不得不使用BMP,F(xiàn)在,EJB2.0,在這個方面的功能已經(jīng)非常強大了,我們完全可以享受容器帶來的便利,而將大部分精力放在實現(xiàn)更加復(fù)雜的業(yè)務(wù)邏輯方面了。
在JDO中,您同樣可以通過PersistenceManager來實現(xiàn)同樣的目標(biāo),例如,您想把一個Customer對象保存到數(shù)據(jù)庫中,可以采用類似于下面的代碼:
Customer customer=new Customer(……); PersistenceManager PM=PMFactory.initialize(……); Pm.persist(customer);
代碼同樣非常簡明和直觀,沒有一大堆數(shù)據(jù)庫操縱的代碼,也不容易發(fā)生差錯。
Websharp的方案
Webshap為數(shù)據(jù)存取的類定義了IEntityDAO接口,該接口的定義如下:
public interface IEntityDAO { void InsertEntity(EntityData entity); void UpdateEntity(EntityData entity); void DeleteEntity(EntityData entity); EntityData FindByPrimaryKey(object KeyValue); }
對于每一個實體類,可以通過擴展這個接口來實現(xiàn)數(shù)據(jù)訪問的類。但是,由于這個接口沒有提供任何實現(xiàn)方法,因此,到具體每個實現(xiàn)類的時候,如果是直接擴展自這個接口,實現(xiàn)的代碼還必須手工填寫。為了提高開發(fā)效率,減少代碼編寫量和出現(xiàn)Bug的可能性,框架提供了AbstractSingleTableDAO和AbstractMultiTableDAO.cs類,這兩個類擴展自IEntityDAO,分別實現(xiàn)了針對單個數(shù)據(jù)庫表和多個數(shù)據(jù)庫表的數(shù)據(jù)庫訪問方法,并且,實現(xiàn)了IDisposable接口。這樣,我們在實際編寫代碼的時候,只需要繼承自這兩個類就可以了。 例如,Customer類的數(shù)據(jù)存取類可以定義如下:
public class CustomerEntityDAO:AbstractSingleTableDAO
然后,就可以在代碼中這么使用:
Customer customer=...... using(CustomerEntityDAO CDO=new CustomerEntityDAO()) { CDO.UpdateEntity(customer); }
更加一般的,Wensharp也提供了PersistenceManager類,可以用于將EntityData中的數(shù)據(jù)存入數(shù)據(jù)庫。這個類包含了兩個方法:PersistEntity和DeleteEntity。如果不想為某個實體類編寫專門的DAO類,那么,也可以使用這個類來操縱實體對象。不過,目前,只支持映射成單個表的對象的自動存貯。下面是一個例子:
PersistenceManager pm=PersistenceManager.Initial(); pm. PersistEntity(entity);
為了封裝不同數(shù)據(jù)庫的操作,統(tǒng)一的數(shù)據(jù)庫訪問接口是必須的。關(guān)于編寫通用數(shù)據(jù)庫訪問類的內(nèi)容,可以參見拙作:《使用設(shè)計模式構(gòu)建通用數(shù)據(jù)庫訪問類》。
在這個部分,另外需要注意的是,為了保證數(shù)據(jù)存儲的完整性,應(yīng)當(dāng)考慮事務(wù)處理的功能。J2EE、JDO和Websharp都支持在數(shù)據(jù)存儲的時候使用事務(wù)處理。
業(yè)務(wù)邏輯的處理
有了上面的工作,我們就可以把這些對象組合起來,編寫我們的業(yè)務(wù)邏輯。在面向?qū)ο蟮南到y(tǒng)中,業(yè)務(wù)邏輯表現(xiàn)為對象之間的交互。在一些簡單的系統(tǒng)中,沒有復(fù)雜的業(yè)務(wù)邏輯,只是一些數(shù)據(jù)的維護(hù)工作,那么,有了上面兩個部分的工作,我們實際上可能已經(jīng)忘成了大部分的工作。
在這個部分,由于不同系統(tǒng)之間業(yè)務(wù)邏輯千差萬別,基本上沒有辦法提供統(tǒng)一的模式。但是,應(yīng)當(dāng)注意的是,在同一個系統(tǒng)中,采用基本一致的策略是非常必要的,這有助于消除項目內(nèi)部的不一致性,使項目更加可控。甚至于,這些策略可以擴展成公司部分、甚至所有項目的策略。
值得指出的是,很多人在這個部分操縱數(shù)據(jù)庫,把業(yè)務(wù)邏輯處理等同于數(shù)據(jù)庫操作,這是不可取的。在業(yè)務(wù)邏輯處理中,處理的應(yīng)該是對象,而不是直接同數(shù)據(jù)庫打交道,這樣,才能獲得更好的系統(tǒng)結(jié)構(gòu)。 在業(yè)務(wù)邏輯處理部分,由框架提供一些支撐的服務(wù)是非常必要的。這其中,最重要的一點就是事務(wù)的處理。業(yè)務(wù)邏輯的處理過程,會涉及到多個對象之間的交互,以及多次同數(shù)據(jù)庫的交互。為了保證處理過程的完整性,必須使用事務(wù)處理的方法?蚣鼙仨氈С质聞(wù)處理。
事務(wù)處理的功能,基本上有兩種選擇:使用基于數(shù)據(jù)庫連接的事務(wù)、使用外部事物處理服務(wù)。
使用基于數(shù)據(jù)庫連接的事務(wù),事務(wù)處理的性能相對比較高,但是,當(dāng)系統(tǒng)涉及到多個數(shù)據(jù)庫之間的交互時,基于數(shù)據(jù)庫連接的事務(wù)便無能為力了。而使用專用的事務(wù)處理服務(wù),能夠適應(yīng)更多的情況,并且,有測試表明,隨著數(shù)據(jù)處理量的上升,兩者之間的性能差異會逐漸減小。
在J2EE中,容器提供了事務(wù)處理的能力。在.Net平臺上,事務(wù)處理是通過Windows COM+服務(wù)來提供的。在Websharp中,對COM+服務(wù)做了一個簡單的封裝。同時,也能夠使用基于數(shù)據(jù)庫連接的事務(wù)。
下面是一個簡單的例子,表示了一張入庫單入庫的過程,在這個過程中,需要修改入庫單上每種產(chǎn)品的現(xiàn)有庫存量:
public void StoreIntoWarehouse(EntityData insertForm) { insertForm.SetCurrentTable("FormDetail"); TransactionManager transManager=new TransactionManager(); ProductEntityDAO productDAO=new ProductEntityDAO(true); FormEntityDAO formDAO=new FormEntityDAO(true); try { if(insertForm.CurrentTable.Rows.Count>0) do { string productID=insertForm["ProductID"].ToString(); decimal inCount=insertForm.GetDecimal("InCount"); EntityData product=productDAO.FindByPrimaryKey(productID); product["CurrentCount"]=product.GetDecimal("CurrentCount")+inCount; transManager.AddMethod( new TransactionManagedFunction(productDAO.UpdateEntity),product); }while(insertForm.Next()); transManager.AddMethod( new TransactionManagedFunction(formDAO.InsertEntity),insertForm); transManager.ExecuteMethods(); } catch(Exception ee) { throw ee; } finally { productDAO.Dispose(); insertForm.Dispose(); } }
業(yè)務(wù)服務(wù)的提供
業(yè)務(wù)外觀層(Business Facade)的目的,是隔離系統(tǒng)功能的提供者和使用者,更明確地說,是隔離業(yè)務(wù)邏輯的軟件的用戶界面(可以參見Facade設(shè)計模式)。這一層沒有任何需要處理的邏輯,只是作為后臺邏輯處理和前端用戶界面的緩沖區(qū),以達(dá)到如下目的
◆將用戶界面和系統(tǒng)業(yè)務(wù)邏輯處理分開,這樣,當(dāng)業(yè)務(wù)邏輯發(fā)生變化時,不用修改客戶端程序,是一種支持變化的設(shè)計方法。
◆使同一個業(yè)務(wù)邏輯能夠處理不同的客戶端請求。例如,可以將Facade設(shè)計成Web Service,這樣,可以同時為傳統(tǒng)的WinForm客戶端程序、Web程序以及其他外部系統(tǒng)提供服務(wù),而使用相同的應(yīng)用服務(wù)層,同時,也可以實現(xiàn)系統(tǒng)的分布式部署。關(guān)于如何做到這一點,可以參見本文所附的Demo程序。
◆作為系統(tǒng)不同模塊之間的調(diào)用接口。一個系統(tǒng)通常會包含很多模塊,這些模塊相對獨立,又可能互相調(diào)用。為了減少各個不同部分之間的耦合度,必須采用一定的設(shè)計方法,F(xiàn)acade設(shè)計模式就是非常有效的一種,也是業(yè)務(wù)外觀層的基礎(chǔ)。
◆有利于項目團(tuán)隊的分工協(xié)作。業(yè)務(wù)外觀層作為一個訪問接口,將界面設(shè)計人員和邏輯設(shè)計人員分開,使得系統(tǒng)的開發(fā)可以實現(xiàn)縱向的分工,不同的開發(fā)人員可以關(guān)注自己的領(lǐng)域而不會受到干擾。
業(yè)務(wù)外觀層的代碼框架,在系統(tǒng)分析和設(shè)計完成后就可以完成,他需要提供的方法,就相當(dāng)于在界面設(shè)計人員和邏輯設(shè)計人員之間簽訂了一個協(xié)議,他雖然沒有實現(xiàn)任何邏輯,但是,他的引入,能使系統(tǒng)的開發(fā)更加有條理,更加簡明。套用《設(shè)計模式》上的一句話,就是,"任何問題,都可以通過引入一個中間層來得到簡化"。
剪裁和取舍
以上四個層次,對于大型的應(yīng)用軟件系統(tǒng)來說,是非常必要的。但是,對于一些小型的應(yīng)用軟件系統(tǒng),如果完全按照以上的層次來做,可能反而會影響工作效率。因此,針對不同的系統(tǒng),可以對架構(gòu)進(jìn)行一定的剪裁。
數(shù)據(jù)實體層和實體控制層,是每個應(yīng)用軟件系統(tǒng)所必需的,顯然無法裁減。對于業(yè)務(wù)邏輯層和業(yè)務(wù)外觀層,根據(jù)實體情況,可以進(jìn)行如下裁減:
◆如果系統(tǒng)沒有復(fù)雜的業(yè)務(wù)邏輯,而只是一些數(shù)據(jù)的操作,或者業(yè)務(wù)邏輯特別少,那么,可以省略業(yè)務(wù)邏輯層,而將相關(guān)的功能移至實體控制層。
◆如果不考慮多種客戶端的情況,也不考慮分布式部署的問題,系統(tǒng)的模塊又很少,不會產(chǎn)生模塊間緊耦合的情況,那么,可以不使用業(yè)務(wù)外觀層,而讓用戶界面程序直接訪問業(yè)務(wù)功能。
在上面的論述中,對于每個層次,都說明了可以選擇的多種方案,每一種方案都有他的優(yōu)點和缺點,在具體開發(fā)的過程中,需要根據(jù)具體情況加以取舍。
系統(tǒng)外的話
應(yīng)用軟件系統(tǒng)架構(gòu),是軟件工程的重要組成部分。設(shè)計一個好的框架,其目的很明確,那就是,在目前還沒有"銀彈"之前,盡最大的可能,提高軟件開發(fā)的效率和軟件質(zhì)量,把不必要的工作和容易出錯的工作,交給框架去處理。
應(yīng)用服務(wù)層,在軟件系統(tǒng)中,是一個非常復(fù)雜的部分,乍看之下,沒有任何規(guī)律可行,給人無從下手的感覺。我們的目標(biāo),就是盡量化無規(guī)律為有規(guī)律,把有規(guī)律的東西提取出來,形成規(guī)范,從而減少今后的開發(fā)工作量。其方法,就是對系統(tǒng)進(jìn)行合理的分層,這樣,系統(tǒng)的層次清晰了,每個層次完成的功能就比較單一,就意味著每個層次的都相對更有規(guī)律可循,這樣,我們就可以把這些有規(guī)律的東西交給框架去執(zhí)行,或者,開發(fā)一個輔助工具,來完成這部分的代碼編寫工作。Websharp就提供了這樣一個代碼自動生成的工具。這個工具被設(shè)計成Visual Studio.Net集成開發(fā)環(huán)境的插件,在實際開發(fā)過程中,能夠提供很多便利。這是系統(tǒng)層次清晰帶來的另外一個好處。
對于一個軟件公司來說,統(tǒng)一的系統(tǒng)框架的意義不僅僅在于軟件開發(fā)的本身。一個統(tǒng)一的系統(tǒng)框架,也是公司知識管理的重要組成部分。公司如果有一個或有限個數(shù)的明確的軟件框架,那么,這些框架就可以成為凝結(jié)公司開發(fā)人員經(jīng)驗、智慧的載體,并且可以在不斷的實踐中加以充實和完善。由于公司的軟件系統(tǒng)的框架比較統(tǒng)一,那么當(dāng)某個項目更換或增加開發(fā)人員的時候,后來的人也能夠比較容易接手,這對于公司的開發(fā)管理是具有非常重要的意義的。
關(guān)于系統(tǒng)框架同知識管理的關(guān)系的內(nèi)容,因為不是本文的重點,限于篇幅的關(guān)系,所以不再贅述,會另文加以說明。
結(jié)語
應(yīng)用軟件系統(tǒng)的應(yīng)用服務(wù)層是非常復(fù)雜的,為了使得系統(tǒng)的結(jié)構(gòu)更加清晰,找出其中規(guī)律性的東西,就需要我們對系統(tǒng)做進(jìn)一步的層次劃分。對于每個層次,都可以有多種設(shè)計的策略,每一種策略都不可能做到盡善盡美,這就需要我們在實際中加以取舍。
限于本人的認(rèn)識和水平,文章中所說觀點和方法或有不當(dāng)之處,還請大家能夠指教,一起探討。
附:使用Websharp中間件開發(fā)的Demo程序一份。
作者簡介:孫亞民,1998年畢業(yè)于南京大學(xué),目前中國科技大學(xué)碩士再讀,蘇州某軟件公司技術(shù)總監(jiān),可以通過sunny_y_m@163.com 同他聯(lián)系。
出處:賽迪網(wǎng)
責(zé)任編輯:cjj
◎進(jìn)入論壇網(wǎng)絡(luò)編程版塊參加討論
|