關於DDD的理論知識總結,可參考這篇文章。html
文章地址:http://dddcommunity.org/library/vernon_2011/,該地址中包含了一篇關於介紹如何有效的設計聚合的一些原則,共3個pdf文件。該文章中指出瞭如下幾個聚合設計的原則:node
上面這幾條原則,做者經過一個例子來逐步闡述。下面我按照個人理解對每一個原則作一個簡單的描述。git
這個原則,就是強調聚合的真正用途除了封裝咱們自己所關心的信息外,最主要的目的是爲了封裝業務規則,保證數據的一致性。在我看來,這一點是設計聚合時最重要和最須要考慮的點;當咱們在設計聚合時,要多想一想當前聚合封裝了哪些業務規則,實現了哪些數據一致性。所謂的業務規則是指,好比一個銀行帳號的餘額不能小於0,訂單中的訂單明細的個數不能爲0,訂單中不能出現兩個明細對應的商品ID相同,訂單明細中的商品信息必須合法,商品的名稱不能爲空,回覆被建立時必需要傳入被回覆的帖子(由於沒有帖子的回覆不是一個合法的回覆),等;github
這個原則,更多的是從技術的角度去考慮的。做者經過一個例子來講明,該例子中,一開始聚合設計的很大,包含了不少實體,可是後來發現由於該聚合包含的東西過多,致使多人操做時併發衝突嚴重,致使系統可用性變差;後來開發團隊將原來的大聚合拆分爲多個小聚合,固然,拆分爲小聚合後,原來大聚合內維護的業務規則一樣在多個小聚合上有所體現。因此實現了既能解決併發衝突的問題,也能保證讓聚合來封裝業務規則,實現模型級別的數據一致性;另外,回覆中的一位道友「殤、凌楓」提到,聚合設計的小還有一個好處,就是:業務決定聚合,業務改變聚合。聚合設計的小除了能夠下降併發衝突的可能性以外,一樣減小了業務改變的時候,聚合的拆分個數,下降了聚合大幅重構(拆分)的可能性,從而能讓咱們的領域模型更能適應業務的變化。數據庫
這個原則,是考慮到,其實聚合之間無需經過對象引用的方式來關聯;架構
這個原則主要的背景是:若是用CQRS+Event Sourcing的架構來實現DDD,那聚合之間由於經過Domain Event(領域事件)來實現交互了,因此一樣也不須要聚合與聚合之間的對象引用,同時也不須要領域服務了,由於領域服務已經被Process(流程聚合根)和Process Manager(流程管理器,無狀態)所替代。流程聚合根,負責封裝流程的當前狀態以及流程下一步該怎麼走的邏輯,包括流程遇到異常時的回滾處理邏輯;流程管理器,無狀態。負責協調流程中各個參與者聚合根之間的消息交互,它會接受聚合根產生的domain event,而後發送command。另一方面,因爲CQRS的引入,使得咱們的domain只須要處理業務邏輯,而不須要應付查詢相關的需求了,各類查詢需求專門由各類查詢服務實現;因此咱們的domain就能夠很是瘦身,僅僅只須要經過聚合根來封裝必要的業務規則(保證聚合內數據的強一致性)便可,而後每一個聚合根作了任何的狀態變動後,會產生相應的領域事件,而後事件會被持久化到EventStore,EventStore用來持久化全部的事件,整個domain的狀態要恢復,只須要經過Event Sourcing的方式還原便可;另外,當事件持久化完成後,框架會經過事件總線將事件發佈出去,而後Process Manager就能夠響應事件,而後發送新的command去通知相應的聚合根去作必要的處理;併發
上面這個過程能夠在任何一個CQRS的架構圖(包括enode的架構圖)中找到,我這裏就不貼圖了。enode中對經典的轉帳場景用這種思路實現了一下,有興趣能夠去下載enode源代碼,而後看一下其中的BankTransferSample這個例子就清楚了。另外,由於事件的響應和Command的發送是異步的,因此,這種架構下,聚合根的交互是異步的;框架
須要再次強調的一點是,聚合若是隻須要關注如何實現業務規則而不須要考慮查詢需求所帶來的好處,那就是咱們不須要在domain裏維護各類統計信息了,而只要維護各類業務規則所潛在的必須依賴的狀態信息便可;舉個例子,假如一個論壇,有版塊和帖子,之前,咱們可能會在版塊對象上有一個帖子總數的屬性,當新增一個帖子時,會對這個屬性加1;而在CQRS架構下,domain內的版塊聚合根無需維護總帖子數這個統計信息了,總帖子數會在查詢端的數據庫獨立維護;dom
首先,什麼是狀態?很簡單,好比一個商品的庫存信息,那麼該庫存信息有一個商品的數量這個屬性,表示當前商品在庫存中還有多少件;那麼咱們爲何須要記錄該屬性呢?也就是爲何須要記錄這個狀態呢?由於有業務規則的存在。以這個例子爲例,由於存在「商品的庫存不能爲負數」這樣的一個業務規則,那這個規則若是要能保證,首先必須先記錄商品的庫存數量;由於商品的庫存數量是會隨着商品的賣出而減小的,而減小就是經過:Product.Count = Product.Count - 1這樣的邏輯運算來實現;這個邏輯運算要能運行的前提就是商品要有庫存信息。從這個例子咱們不難理解,一個聚合根的不少狀態,不是平白無辜設計上去的,而是某些業務規則潛在的要求,必需要設計這些狀態才能實現相應的業務規則;這樣的例子還有不少,好比銀行帳號的餘額不能小於0,致使咱們的銀行帳號必需要設計一個當前餘額的屬性;異步
另一個緣由是,看起來像是廢話,呵呵。就是:由於咱們關心這些信息,因此須要設計在當前聚合上;好比,以一個論壇的帖子爲例,做爲一個帖子,咱們一般都會關心帖子的標題、描述、發帖人、發帖時間、所屬版塊(若是論壇有版塊這個概念的話);因此,咱們就會在帖子聚合根上設計出這些屬性,以表達咱們所關心的這些信息的狀態;
下面在從偏哲學的角度表達一下對象的概念吧:
上面只是簡單提到,聚合的設計應該多考慮它封裝了哪些業務規則這個問題。下面我想再多講一點個人一些想法:
仍是以論壇的帖子爲例,建立一個帖子時,有一個業務規則,那就是帖子的發帖人、標題、描述、所屬板塊(若是論壇有板塊這個概念的話)都不能爲空或無效的值,由於這些信息只要有任何一個無效,那就意味着被建立出來的帖子是無效的,那就是沒有保證業務規則,也就沒辦法談領域模型的數據一致性了;若是像以往的三層貧血架構,那帖子只是一個數據的載體,不包含任何業務規則,帖子會先被構造一個空的帖子對象出來,而後咱們給這個空帖子對象的某些屬性賦值,而後保存該帖子對象到數據庫;這種設計,帖子對象只是一個數據的容器,它徹底控制不了本身的狀態,由於它的狀態都是被別人(如service)去修改的;這樣的設計,至關因而沒有把業務規則封裝在業務對象內部,而是轉移到了外部service中,雖然這樣一般也沒問題,事實上咱們大部分人都一直在這麼幹,由於這樣幹寫代碼很隨意,也很高效,呵呵。
GRASP九大模式中有一個面向對象的模式叫信息專家模式,不知道你們有了解過沒有,該模式的描述是:將職責分配給擁有執行該職責所需信息的對象;這個模式告訴咱們,若是一個對象負責維護一些信息,那它就有職責維護好這些信息。體現到對象的屬性上,那就是這個對象的屬性不能被外部隨便更改,對象本身的屬性必須本身負責維護修改。構造函數和普通的方法都會改變對象的狀態,因此,咱們對構造函數和對象普通的公共方法,都要秉持這個原則;這點很是重要,不然,若是像貧血模型那樣,那對象就不叫對象了,而只是一個普通的容納數據的容器而已,和數據庫裏的一條記錄也無本質差異了。實際上,在我看來,這也是DDD中的聚合區別於貧血模型中的實體的最大的地方。聚合不只有狀態,還有嚴格維護好本身狀態的各類方法,包括構造函數在內;而貧血模型,則只有狀態,沒有行爲;
這個問題,沒有很是清晰的放之四海而皆準的肯定方法,個人想法是:
這一點在最上面的幾個原則中,實際上已經提到過一點,那就是儘可能設計小聚合,這裏的出發點主要是從技術的角度去思考,爲了下降對公共對象(大聚合)的併發修改,從而減少併發衝突的可能性,從而提升系統的可用性(由於系統用戶不會常常由於併發衝突而致使它的操做失敗);關於這一點,我還想再舉幾個例子,來講明,其實要實現各類業務規則,能夠有多種聚合的設計方式,大聚合只是其中一種;
好比,帖子和回覆,你們都知道一個帖子有多個回覆,沒有帖子,回覆就沒有意義;因此不少人就會認爲帖子應該聚合回覆;但實際上不須要這樣,若是你這樣作了,那對於一個論壇來講,同一個帖子被多我的同時回覆的可能性是很是高的,那這樣的話,多我的同時回覆一個帖子,就會致使多我的同時修改同一個帖子對象,那就致使你們都回復不了,由於會有併發衝突或者數據庫事務的等待超時,由於你們都在修改同一個帖子聚合根;實際上若是咱們從業務規則的角度去思考一下,那能夠發現,其實帖子和回覆之間,只有一個簡單的規則,那就是回覆一旦被建立,那他所對應的帖子不能被修改便可;這樣的話,要實現這個規則其實很簡單,把回覆做爲聚合根,而後把帖子傳入回覆聚合根的構造函數,而後回覆保存帖子ID,而後回覆將帖子ID設置爲不容許外部修改(private set;便可),這樣咱們就實現了這個業務規則,同時還作到了多人同時推一個帖子回覆時,不會對同一個帖子對象就併發修改,而是每一個回覆都是並行的往數據庫插入一條回覆記錄便可;
因此,經過這個例子,咱們發現,要實現領域模型內的各類業務規則,方法不止一種,咱們除了要從業務角度考慮對象的內聚關係外,還要從技術角度考慮,可是無論從什麼角度考慮,都是以實現所要求的業務規則爲前提;
從這個例子,咱們其實還發現了另一件有意義的事情,那就是一個論壇中,發表帖子和發表回覆是兩個獨立的業務場景;一我的發表了帖子,而後可能過了一段時間,另外一我的對該帖子發表了回覆;因此將帖子和回覆都設計爲獨立的很容易理解;這裏雖然帖子和回覆是一對多,回覆離開帖子確實也沒意義,可是將回復設計在帖子內沒任何好處,反而讓系統的可用性下降;相反,像上面提到的關於建立任務時同時上傳一些附件的例子,雖然一個任務也是對應多個附件信息,可是咱們發現,人物的附件信息老是隨着任務被建立或修改時,一塊兒被修改的。也就是說,咱們沒有獨立的業務場景須要獨立修改任務的某個附件信息;因此,沒有必要將任務的附件信息設計爲獨立聚合根;
enode提供了一個基於DDD+CQRS+Event Sourcing+In Memory+EDA這些技術的應用開發架構;
使用enode,將會迫使你思考如何設計聚合,如何經過流程實現聚合之間的異步交互;迫使你思考如何定義domain event,將領域內的狀態更改顯式化;迫使你將外部對領域的各類操做顯式化,即定義出各類command;迫使你將command side和query side的數據分離和架構分離,技術分離。減小的是,咱們沒必要再設計unit of work,沒必要設計domain service,沒必要讓聚合設計各類非第一手的冗餘的統計信息;