前面介紹了應用程序框架的一個重要組成部分——公共操作類,並提供了一個數據類型轉換公共操作類作為示例進行演示。下面准備介紹應用程序框架的另一個重要組成部分,即體系架構支持。你不一定要使用DDD這樣的架構,使用單層架構和普通三層架構一樣可以,不過你如果希望獲得更進一步的復用性和封裝度,使用更加面向對象的技術是必經之程。
我在2010年以前還在使用古老的ASP.NET WebForm和原始的Ado.Net。之前我有個觀念:.NET技術發展太快,跟着微軟屁股后面跑太累,所以只使用它一些原始的東西,自己封裝一下也能滿足工作上的需求。對於像Linq這樣的技術只是隨便看了下,特別是當時很多人告訴我Linq已死,千萬別學,我當時很喜歡這樣的言論,因為不學習新知識就有了充分的理由。
到了2010年,我有一次上博客園,瀏覽了一些文章,發現充滿各種縮寫和名詞,什么Dto、工作單元一類,我才知道新一代的.Net技術已經開始普及,我已經Out了。之后我開始學習EF和MVC,在剛開始接觸EF的時候,我從一些博客了解到,為了發揮EF的威力,必須使用DDD進行設計。為了掃清攔路虎,我購買了幾本DDD的書來學習,學習過程中才發現面向對象和敏捷開發才是關鍵,於是開始大量購書,一發不可收拾,四年時間買了接近兩百本后,終於把基礎補起來一點。
DDD的核心思想是描述如何使用面向對象的方法對業務領域建模,怎樣獲得更好的領域模型。雖然看了不少DDD的資料,但還是感覺它異常抽象,另外面向對象的思想也很難進步,可能和前些年的編程習慣有關,已經習慣於從數據的角度考慮問題,形成了思維定勢。
DDD雖然抽象,但它還是提供了一些技術上的支持。大部分人都是從DDD分層架構入手來進行學習和實踐,當然,DDD並不是分層架構,分層架構只是DDD的一個技術構造。下面簡單介紹一下我對DDD分層架構的理解,由於我使用DDD的時間不長,我所描述的觀點都是我自己的一些開發經驗,不一定正確,歡迎各位高手批評指正,共同進步。
DDD分層架構總體上和三層架構相似,不過對各層提出了更具體的職責和構造塊。我也經常與一些在使用DDD分層架構的朋友交流,我問他們DDD分層架構與普通三層架構有何區別,大部分人都感覺差不多,除了一些名詞術語有所變化。如果你也是這個感覺,那么可能本文對你是有幫助的,因為我明顯感覺出它們之間有所不同。
DDD分層架構與傳統三層架構示意圖如下。
(領域驅動設計分層架構示意圖)
(傳統三層架構示意圖)
DDD分層架構與傳統三層架構最重要的區別可能是重心不同,即傳統三層架構的重心在業務邏輯層,而DDD分層架構的重心在領域層。
面向對象設計的核心是基於業務概念建模,並映射到代碼中,這樣的好處是減輕程序員將業務概念轉換到技術的負擔,因為更容易理解。傳統三層架構雖然也把業務概念轉換到實體層的Model對象,但實體層只是一個輔助設施,這些Model只是用來裝數據的容器,作用並不顯著。DDD分層架構把領域層提到核心地位,這些Model成為業務邏輯的一個主要放置場所。
使用DDD分層架構的第一個好處就是業務邏輯高度內聚到領域層,換句話說,如果有邏輯問題,找領域層就對了。對於這一點,有些人認為傳統三層架構也可以,找業務邏輯層不是一樣嗎?這可能是大多數對領域驅動設計分層架構認識無法突破的關鍵。
雖然你可以按照分層架構的要求,把全部業務邏輯都寫到BLL層,但你無法精確定位你需要的業務邏輯究竟處於什么位置,換句話說,你需要業務邏輯的一個唯一訪問點。由於你無法輕易找到業務邏輯的訪問點,所以產生冗余代碼就再所難免,一段相同或相似的冗余代碼會在多個地方產生,從而導致可維護性的降低。通過強制約束代碼和目錄規范以及提取公共方法可以緩解部分問題,但要從根本上解決,你還得向面向對象求救。
那么,哪里是業務邏輯最好的落腳點,最直觀,最容易被大家想到的唯一訪問點在哪呢?比如你要處理一個訂單,讓你到其它地方去找處理訂單的代碼,你自然找起來困難。那么如果這段代碼處於訂單實體的內部,情況就大不相同了,你可以在最短的時間內找到它。在領域實體中內聚業務邏輯,可以為你創建一個業務邏輯的唯一訪問點。大家以后需要某個邏輯的時候,先看看實體中有沒有自己需要的,這樣就能顯著降低代碼冗余,從而更好維護。
所以,我的第一條DDD使用經驗就是,使用充血模型,將業務邏輯盡量放到領域實體中。充血模型有很多爭論,不過你大可不必理會別人的說法,自己實踐才能出真知。用得不爽,你后面不用就是了,對你基本沒啥影響。目前我使用充血模型,感覺它主要的問題是,如果采用分布式架構,比如中間采用WCF遠程調用,需要通過一層專門的DTO來進行傳輸,而且需要增加一個遠程外觀的服務,會導致工作量上升。
當把充血模型用起來以后,下一步是要把聚合用起來。聚合這個概念很好理解,就是包含關系。在UML中有兩種包含關系,第一種叫聚合,表示比較弱的包含關系,聚合內部的東西在外面可以直接訪問。第二種叫組合,即組成聚合,是很強的包含關系,表示外部的對象由內部的多個子對象組成,內部的子對象在外面不能直接訪問,必須通過外部的對象間接引用。DDD雖然用了聚合這個詞,但它表示UML中的組成聚合,所以它把外部的對象稱為根,即聚合根,要訪問內部對象,必須先訪問聚合根。
概念上的理解,除了能吹吹牛以外,沒多大幫助。我在剛接觸DDD的時候,也能理解聚合的概念,說起來一樣口沫橫飛,但真正用起來過了差不多一年。除了我反應比較遲鈍以外,還有一個原因是被之前以數據為中心的思維定勢所束縛。
我也經常下載一些DDD的Demo來學習,但是這些例子大多都非常簡單,所以我主要還是依靠看書和自己摸索。我剛開始的用法是一個表對應一個領域實體,每個領域實體對應一個倉儲。我在使用的過程中,隱隱發現哪里不對,但是無法找出具體的原因。經過大半年,我也使用DDD開發了幾個簡單的項目,逐步積累了一些經驗,在一次看書的時候,我突然領悟到我的DDD用法主要毛病是依賴關系混亂,而解決這些依賴關系的手段就是聚合。
聚合的主要影響是顯著減少倉儲數量,以及集中管理高度依賴的相關實體。把高度相關的實體內聚到一個聚合中,可以把這些依賴關系封裝到一個更小的空間,外部只與聚合根打交道,與聚合內部子對象的依賴關系就會明顯降低。一個聚合對應一個倉儲,而不是一個實體對象一個倉儲,可以減少倉儲數量,從而進一步降低依賴關系。
后面我重新閱讀了一些博客和書籍,發現別人其實都說清楚了,只是自己當時看過去沒有理解而已,這真是紙上得來終覺淺 絕知此事要躬行。
我的第二條DDD使用經驗是,把高度相關的實體封裝到聚合中,為每個聚合根創建一個倉儲。
觀察上面的DDD分層架構示意圖,會發現領域層只依賴於應用程序框架服務,倉儲采用了接口分離模式將實現和接口分離到不同的程序集,領域層中只包含倉儲的接口,這個設計讓領域層非常純凈,和外部的依賴關系降到最低。這對我們意味着什么?更低的依賴讓我們可以方便的對業務邏輯進行單元測試,特別是采用了TDD方式的話,這一點將顯得尤其重要。我們可以在單元測試中使用模擬框架對倉儲以及外部依賴進行模擬測試,從而大幅度提升業務邏輯的穩定性和健壯性。
另外,觀察傳統三層架構,業務邏輯層一般直接依賴數據訪問層,讓單元測試變得困難,從而轉向更粗粒度的集成測試。
通過上面的分析,可以看到采用DDD分層架構可以獲得比傳統三層架構更好的可復用性、可維護性、可測試性等。
當然不可能把所有業務邏輯全部放入領域實體中,有些功能需要操作多個實體,或者需要使用某些設計模式,這時候需要使用領域服務。這里的要點是盡量把領域服務的操作委托給領域實體,因為這樣業務邏輯可以更加集中。
DDD分層架構還有一些構造塊,我會在后面的文章詳細介紹。如果沒有介紹到的,說明我還處於學習和摸索階段,還沒有多少心得,等我有些經驗以后再告訴大家。
現在來總結一下。
使用DDD分層架構有哪些好處
- 幫你更集中的管理業務邏輯。
- 幫你降低各層間,以及各業務模塊間的依賴關系。
- 幫你更方便的進行單元測試。
我的DDD分層架構使用經驗
- 使用充血模型,將業務邏輯盡量放到領域實體中,領域實體為業務邏輯提供一個唯一訪問點。
- 不能放入領域實體的邏輯,盡量放到領域服務,總之,業務邏輯應該高度內聚到領域層。
- 把高度相關的實體封裝到聚合中,為每個聚合根創建一個倉儲。
最后,提醒一下,我們使用一些DDD分層架構構造塊,可能並不算真正用上了DDD。但是,我們的目標是使用DDD嗎?不是,我們的目標是把業務邏輯做得更穩定,更好維護。所以不用在意自己使用的技術正不正宗,標不標准,只要比以前更好,就應該堅持下去。
.Net應用程序框架交流QQ群: 386092459,歡迎有興趣的朋友加入討論。
謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/xiadao521/