- 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擷取到操作的相關聚合根,然後調用聚合根完成整個業務操作;比如資金轉帳,這是經典的領域服務的例子;再比如在調用某個聚合根做一個資料更新之前需要先判斷一些業務規則,但是這些判斷規則不能在該聚合根内做,因為這樣做可能會導緻聚合根依賴于外部的領域服務或倉儲,此時,應該交給領域服務來完成規則校驗和聚合根資料更新的整個過程。
參考資料: