近接到一個臨時任務(wù):幫外國某知名公司分析一個項目架構(gòu)。這個項目是兩年前開發(fā)的,并且經(jīng)過了幾次升級。主要功能是管理客戶、合作伙伴資料,提供在線業(yè)務(wù)等等,具體細(xì)節(jié)不用多說。
據(jù)客戶說,他們在使用本系統(tǒng)的過程中發(fā)現(xiàn)了很多的問題,覺得已經(jīng)不再滿足他們的需求,希望我們能幫助他們評估一下當(dāng)前的系統(tǒng)有哪些架構(gòu)上的問題,并幫助他們發(fā)現(xiàn)未來可能發(fā)生的問題,從而決定是否需要開發(fā)新的系統(tǒng)
客戶提供了很詳細(xì)的文檔,包括業(yè)務(wù)說明,系統(tǒng)架構(gòu),技術(shù)要點,部署方案等等。看完文檔,對系統(tǒng)和客戶期望有了一定的了解之后,開始干活兒!
系統(tǒng)是采用.Net技術(shù)構(gòu)建的,基于.Net Framework 2.0,使用了ASP.NET, WinForm, WebService等技術(shù),并使用了Enterprise Library中的Data Access, Cache,Log等功能。
我本人負(fù)責(zé)的是架構(gòu)的分析。結(jié)合文檔和源代碼,沒用1個小時,系統(tǒng)架構(gòu)很清晰了。其中發(fā)現(xiàn)了一些很普遍的問題,在這里跟大家分享一下:
1. 分層架構(gòu)
分層架構(gòu)是絕大部分企業(yè)軟件都普遍采用的方案,但由于架構(gòu)師水平的參差不齊,導(dǎo)致很簡單的一個分層,出現(xiàn)了很大的差異。
大家都知道“3層架構(gòu)”或者“多層架構(gòu)”。有點理論里分3層,有點理論里分5層,還有分7層的。其實,在我看來分幾層不重要,重要的是分層的目的。分層是為了什么的?簡單的說一句話:為了便于維護(hù)。
大家都知道,軟件開發(fā)中“變化”才是永恒的。開發(fā)周期是死的(盡管可以一拖再拖,但總有一個發(fā)布的截至日期吧。,但后期的維護(hù)卻是很不變得。不管是發(fā)布補丁也好,更新版本也好,其實都是為了能適應(yīng)軟件發(fā)布之后面臨的各種各樣的“變化”。
好,我們回到分層上來。分層,是為了使系統(tǒng)結(jié)構(gòu)更清晰,系統(tǒng)耦合性變小,使修改一處代碼時,對其它的部分影響小,這樣能以小的代價應(yīng)對變化帶來的麻煩!所以,分層的第一要素,是各層之間屏蔽細(xì)節(jié),降低依賴,使各層具體實現(xiàn)變得透明(這也是SOA的其中一個重要思想)。我們可以通過各種辦法來達(dá)到這個目的,面向接口編程,設(shè)計模式,架構(gòu)模式等等,都可以幫助我們。
而我在此項目中看到的第一個重要的問題是,系統(tǒng)分層不清楚。下面是分層的簡圖:
由上圖可見,系統(tǒng)共分了 三層。但有一個問題,業(yè)務(wù)對象竟然貫穿了三層,這嚴(yán)重違反了分層的初衷。由于業(yè)務(wù)對象對數(shù)據(jù)存取層和展現(xiàn)層都可見,導(dǎo)致如果我們的業(yè)務(wù)對象發(fā)生了變化, 要修改從數(shù)據(jù)存取、業(yè)務(wù)邏輯到展現(xiàn)層,這三個層次的所有相關(guān)代碼。而且這個方案違反了分層的兩個設(shè)計原則:
a. 下層對上層隱藏細(xì)節(jié),只暴露接口。再此,本應(yīng)屬于業(yè)務(wù)邏輯層的業(yè)務(wù)對象被暴露到了展現(xiàn)層。
b. 上層對下層不可見。即下層不知道上層的存在,只提供接口。這里業(yè)務(wù)邏輯層的業(yè)務(wù)對象被數(shù)據(jù)存取層操作,會導(dǎo)致兩個層之間糾纏不清,以至于會出現(xiàn)改動業(yè)務(wù)邏輯會影響數(shù)據(jù)存取方式的荒謬現(xiàn)象。
另外,強類型DataSet也有同樣的問題(本應(yīng)是屬于數(shù)據(jù)存取層的,卻被傳遞到業(yè)務(wù)邏輯層,甚至是展現(xiàn)層)
軟件設(shè)計中有一個很重要的原則是:依賴倒置原則(DIP)
依賴倒置的意思是:調(diào)用者依賴被調(diào)用者的接口,而不是實現(xiàn)。
具體到此處是:業(yè)務(wù)邏輯層應(yīng)該依賴數(shù)據(jù)存取層的接口,而不是具體實現(xiàn)(強類型DataSet)。由于被調(diào)用者編程了抽象,而調(diào)用者變成了“實現(xiàn)”,所以與普通的面向?qū)ο蟮挠^念來說,依賴關(guān)系被倒置了,因此被稱作依賴倒置原則。
依賴倒置在分層結(jié)構(gòu)中是很重要的原則。希望每一個設(shè)計者都時刻把它記在心間吧,呵呵。
2. 面向接口編程
其實這跟上一個問題有密切聯(lián)系。面向接口編程,是“依賴于抽象,而不是實現(xiàn)”的具體手段。不管是模塊內(nèi)部,還是個層次之間,面向接口是消除依賴的基礎(chǔ)。
舉一個簡單的例子:本系統(tǒng)中業(yè)務(wù)邏輯層會調(diào)用數(shù)據(jù)存取層的方法,得到一些數(shù)據(jù)。比如調(diào)用一個PartnerAccess類的GetPartner的方法。PartnerAccess是數(shù)據(jù)存取層的一個具體類,負(fù)責(zé)Patrner表的所有增刪改查操作。而業(yè)務(wù)邏輯層到處充斥著這樣的語法:PartnerAccess partnerac = new PartnerAccess();partnerac.getPartner();
這是一個典型的依賴于具體實現(xiàn)的方案。這樣的后果是,業(yè)務(wù)邏輯層知道每一個數(shù)據(jù)表的數(shù)據(jù)結(jié)構(gòu),甚至是無需知道的細(xì)節(jié),并且對數(shù)據(jù)層的每一個方法都了如指掌,到處都在使用。當(dāng)我們開始修改PartnerAccess的其中一個方法的時候(比如增加一個參數(shù))都要修改業(yè)務(wù)邏輯層的相關(guān)代碼,但誰知道那些代碼都在哪呢?只好重新編譯吧,讓編譯器告訴我們。