架構那點事係列二 - 大話3D
近幾年,架構領域興起了很多新型架構思想。DDD成為繼OOD之後又一個被人津津樂道的設計風格。.這裏結合自己工作實踐,和大家分享一下自己的DDD實踐觀,首先向大家推薦一篇關於DDD的文章(https://msdn.microsoft.com/en-us/magazine/hh547108.aspx.看看微軟的卓越工作,從DataTable到EntityObject. - Net 4.0來了,隨之為我們帶來了EntityFramework)。這裏,我們拋開語言特性,從本質上分析一下DDD的諸多實踐要點。
首先,我們需要知道DDD和OOD有何不同?在此之前,我們先從模型上進行下對比。對象模型,我們都知道它封裝了數據(屬性)和行為(方法和事件)。而領域模型則是駐足在某領域內的對象模型。相應地,它的行為是用於表達業務規則和特定的業務邏輯。更重要的是,每個實體都可能處於某一狀態,並且與一組動態的驗證規則相綁定;領域模型表述的是領域中各個類及其之間的關係。類關係是多樣的,比如組合、聚合、繼承、實現等,而數據模型不是一對多,就是多對多。從領域驅動設計的角度看,數據庫隻不過是存儲實體的一個外部機製,是屬於技術層麵的東西。數據模型主要用於描述領域模型對象的持久化方式,應該是先有領域模型,才有數據模型,領域模型需要通過某種映射而產生相應的數據模型(數據依賴於實體,是實體的狀態,離開實體的數據是毫無意義)。
ok,有了基本認識後,我們再來看一下領域驅動設計所倡導的分層。如圖:
基礎結構層:不解釋了。注意,這部分不會涉及任何業務邏輯。傳統的數據訪問層,也被放在了該層當中,因為數據的讀寫是業務無關的;
領域層:包含了領域對象(實體、值對象)、領域服務以及它們之間的關係。這部分內容的具體表現形式就是領域模型。領域驅動設計提倡富領域模型,即盡量將業務邏輯歸屬到領域對象上,實在無法歸屬的部分則以領域服務的形式進行定義。
應用層:該層不包含任何領域邏輯,但它會對任務進行協調,並可以維護應用程序的狀態.因此,它更注重流程性的東西。在某些領域驅動設計的實踐中,也會將其稱為“工作流層”。
表現層:這個好理解,跟三層裏的表現層意思差不多,但請注意,“Web服務”雖然是服務,但它是表現層的東西.
從上圖還可以看到,表現層與應用層之間是通過數據傳輸對象(DTO)進行交互的,數據傳輸對象是沒有行為的對象,存在的目的隻是為了對領域對象進行數據封裝,實現層與層之間的數據傳遞。為何不能直接將領域對象用於數據傳遞?因為領域對象更注重領域,而DTO更注重數據。不僅如此,由於“富領域模型”的特點,這樣做會直接將領域對象的行為暴露給表現層。
通過上麵的回憶,這裏大致總結出了DDD實踐中需要注意的要點,如下:
(1).構建領域對象
DDD喜歡使用rich poco/pojo。當然,它"富裕"的程度要遠遠大於之前我們所熟悉的ActionForm。除此之外,某些領域模型可以提供用於創建新實例的公共工廠方法(在高並發多構造參數模型下,我會選擇使用Builder模式構建)。如果模型類通常是獨立的並且實際上不是層次結構的一部分,或者用於創建該類的步驟隻是與客戶端相關,則可以使用普通的構造函數。但是,在使用聚合根這樣的複雜對象時,還需要我們實例化其它抽象級別DO。 DDD 引入了工廠對象的方式,這種方式可將客戶端的需求與內部的對象及其關係和規則分離開來。最後,工廠還需要驗證輸入數據。為此,可使用前提條件(如Assert,Validate,Precondition等開源組件)來保證代碼的清晰和高可讀性。還可以使用後置條件來確保返回的實例處於有效狀態,在.net中,常見的有Code Contracts,Data Annotations與VAB(Enterprise Library Validation Application Block)企業庫驗證應用程序塊 等out-of-box technologies。Java中可以考慮比較流行的DBC框架。
(2).識別聚合根.
聚合根是一個通過組合其它實體而得到的實體。聚合根中的對象與外部沒有直接的關聯,也就是不存在這樣的用例—不經過根對象而直接使用這些對象。換句話來說,聚合根負責維護處於有效狀態的子對象並持久化這些對象。更通俗的講,一個聚合是由一些列相聯的Entity和Value Object組成(滿足某些不變性規則),一個聚合有一個聚合根,聚合根是Entity,整個聚合被看成是一個數據修改的單元,也就是說整個聚合內的所有對象要麼同時被保存,要麼都不能保存,否則無法確保聚合內的對象的數據一致性。聚合內的所有實體和值對象應該總是一起被取出來一起被保存,因為一個聚合是一個數據持久化的單元。同時,需要注意兩點:a.聚合不要設計的過大,過大的聚合很難確保不變性,從而很難確保數據的強一致性;b.聚合與聚合之間不要通過引用的方式來關聯,而應該通過ID關聯,這樣具有更好的性能和可伸縮性。
(3).倉儲&領域服務
倉儲應理解為一個在內存中維護一係列聚合根的集合。倉儲提供的接口應該總是接受聚合根或返回聚合根,不能返回聚合內的其他Entity或Value Object。不要把倉儲理解為DAO,倉儲屬於領域模型的一部分,代表了領域模型向外提供接口的一部分,而DAO是表示數據庫向上層提供的接口表示。倉儲的目的也不是為了支持界麵查詢,不要為倉儲設計一些是為了提供顯示數據的接口,倉儲提供的所有接口應該僅為領域模型使用。基本的倉儲接口隻需要三個:Add,Remove,GetById,其他的擴展接口可以根據業務需要擴展接口聲明。
領域服務表示領域模型中的一些業務操作,這些操作通常由多個聚合根或倉儲或其他領域服務相互協作完成。領域服務表示領域模型中的一些業務操作,這些操作通常由多個聚合根或倉儲或其他領域服務相互協作完成,那麼需要為這些操作建立領域服務。首先根據各個聚合的ID獲取到操作的相關聚合根,然後調用聚合根完成整個業務操作;比如資金轉帳,這是經典的領域服務的例子;再比如在調用某個聚合根做一個數據更新之前需要先判斷一些業務規則,但是這些判斷規則不能在該聚合根內做,因為這樣做可能會導致聚合根依賴於外部的領域服務或倉儲,此時,應該交給領域服務來完成規則校驗和聚合根數據更新的整個過程。
參考資料:
1.https://www.infoq.com/cn/articles/ddd-in-practice
2.https://msdn.microsoft.com/en-us/magazine/hh547108.aspx
3.https://msdn.microsoft.com/en-us/library/aa697427(v=VS.80).aspx
4.https://www.cnblogs.com/daxnet/archive/2010/07/07/1772581.html
5.https://www.methodsandtools.com/archive/archive.php?id=97p2
6.https://incubator.apache.org/isis/index.html
7.https://www.slideshare.net/jboner/scalability-availability-stability-patterns
最後更新:2017-04-02 06:52:24