實戰DDD(Domain-Driven Design領域驅動設計:Evans DDD)

NO IMAGE

2004年著名建模專家Eric Evans發表了他最具影響力的著名書籍:Domain-Driven Design –Tackling Complexity in the Heart of Software(中文譯名:領域驅動設計 2006年3月清華出版社譯本,或稱 Domain Driven-Design architecture [Evans DDD])。

  Martin Fowler作序說;“希望本書是一本非常有影響力的書籍,……. Eric最值得我尊敬的一個方面是他敢於討論還未取得成功的事情”,其實,時值今年2006年,DDD開發框架已經層出不窮(如RoR、RIFE、JdonFramework等),我們專案軟體包結構都變成了這樣:xxx.model;xxx.service,DDD思想已經遍地開花,不能再說不成功了。

  DDD是告訴我們如何做好業務層!並以領域驅動設計思想來選擇和合適的框架,現在很多人總是先接觸框架架構,不知道DDD,結果編出的程式很難拓展,業務邏輯都寫在Service中,造成模型失血,成為資料的容器(見這個案例)。這些都是偽善OO,正確的學習順序應該是首先學習DDD,再根據DDD和業務指導如何使用框架,如Spring
Seam等。

  本文以基於JdonFramework開發的JiveJdon3.0說明DDD方法的實戰應用。

  首先必須認識到:領域建模是一種藝術的技術,不是數學的技術,它是用來解決複雜軟體快速應付變化的解決之道(快速適應需求變化的軟體複用)。

  我們知道軟體的產生過程是:分析、設計、程式設計、測試、部署。過去,分析領域和軟體設計是分裂的,分析人員從領域中收集基本概念;而設計必須指明一組能北專案中適應程式設計工具構造的元件,這些元件必須能夠在目標環境中有效執行,並能夠正確解決應用程式出現的問題。 模型驅動設計(Model-Driven Design)拋棄了分裂分析模型與設計的做法,使用單一的模型來滿足這兩方面的要求。這就是領域模型。

  單一的領域模型同時滿足分析原型和軟體設計,如果一個模型實現時不實用,重新尋找新模型。如果模型沒有忠實表達領域關鍵概念時,也必須重新尋找新的模型。 建模和設計成為單個迭代迴圈。將領域模型和設計緊密聯絡。因此,建模專家必須懂設計,會程式設計。

分層架構

  最初層次只分為三層:表現層、業務層和持久層;DDD其實告訴我們如何讓實現業務層!

  一位道友曾經請教層次的職責,對服務Service提出疑問。根據Eric的理論,業務層將細分為兩個層次:應用層和領域層。它們的定義是:應用層:定義軟體可以完成的工作,並且指揮具有豐富含義的領域物件來解決問題,保持精練;不包括業務規則或知識,無業務情況的狀態;
領域層:負責表示業務概念、業務狀態的資訊和業務規則,是業務軟體核心。

  層次之間必須清晰分離,每個層都是內聚的,並且只依賴它的下層,為了實現各層的最大解耦,Ioc模式和Ioc容器是目前最好的選擇,JdonFramework使用基於PicoContainer的Ioc容器實現了各層的鬆耦合;

  Eric特別指出:那種將業務邏輯交由業務介面處理的快速UI方式是旁門左道。希望象C/S結構那樣視覺化拖拖圖形就完成的軟體開發是一種錯誤的方向,開發時快速,難於維護和擴充套件,雖然使用J2EE技術,其實是一種偽多層技術。可惜,有很多國人在瘋狂開發這類工具,大有不撞南牆不低頭之勢,並且瘋狂誤導很多非專業人士,可悲可嘆!如果對這段言論持不同意見,建議你購買”領域驅動設計”這本譯書,見P53頁。

領域模型種類

  傳統模型分為兩種:實體(Entity)和值物件(Value Object),現在服務(Service)成為第三種模型元素。

  實體(Entity)定義:通過一系列連續性(continuity)和標識(identity ID)來定義;個人認為它和分析領域的四色原型中的PPT原型非常類似,可以看成是PPT原型延續。

  實體必須擁有自己的唯一ID,主鍵,如果沒有一個ID標識,為每個例項加上一個具有唯一性ID,可能是內部使用。 如JiveJdon3.0中jdonframework.xml中模型增刪改查CRUD配置定義:

<model key=”forumId”  class=”com.jdon.jivejdon.model.Forum”>
    …..     
</model>

  其中,forumId是模型com.jdon.jivejdon.model.Forum的主鍵,唯一ID,每個模型必須有一個專家。

  值物件(Value Object):如果一個物件代表了領域的某種描述性特徵,且沒有概念性的標識。個人認為它是四色原型中Description原型延續。如果我們只關心模型中一個元素的屬性,那麼把這個元素劃為值物件。值物件是不可變的,不要給它任何標識,避免實體的維護性,降低設計複雜性。我們不關心值物件是哪個例項。

  在JiveJdon3.0中,ForumState是一個值物件,它表示論壇當前最新帖子、論壇的主題數量和帖子數量,它的根物件是Forum,是被內聚嵌入到Forum這個實體模型中的,程式碼如下:

package com.jdon.jivejdon.model;

 

/**
* Forum State ValueObject
* this is a embeded class in Forum. 
* @author <a href=”mailto:[email protected]”>banq</a>
*
*/
public class ForumState { 

  private int threadCount = 0; //主題數量 

  
  private int messageCount = 0;//帖子數量

  private ForumMessage lastPost; //最新帖子

 

  public int getMessageCount() {
    return messageCount;
  }  

  ……

  同樣ForumThreadState是也是一種值物件,根據Eric的值物件設計,ForumThreadState和ForumState是可以合併成一個物件的,值物件中沒有ID等唯一標識。

forum

 

  Eric認為:服務Service是描述領域概念最自然的方式,是四色原型的MI原型的延續, 優秀服務3個特徵:
  1.與領域概念相關的操作行為、但不是實體和值物件中固有的部分。
  2.介面根據領域模型中其他元素定義
  3.操作是無狀態的。

  在JiveJdon3中,com.jdon.jivejdon.service.ForumService和Forum實體模型及其值物件ForumState共同完成領域模型,其中ForumService屬於應用服務層;而後兩者屬於領域層;其他服務ForumMessageService、AccountService和UploadService等都是此類性質。

領域物件的生命週期Scope

  Spring 1.x剛出來時確實忽悠了大家一把,因為他沒有領域物件的生命週期支援,直到Spring 2.0才將如new Bean scope,當初那些瘋狂捧Spring 1.x 臭腳的所謂高手是不是還是基於資料庫驅動的思維,根本沒有真正OO模式思維,當今天JBoss Seam、Scopes等框架開始重視物件生命週期支援後,曾經發生在Jdon社群爭戰硝煙已經過去,成為歷史。

  Eric認為:每個物件獨有器生命週期,一個物件在建立以後,可能要經歷各種不同的狀態,並最終消亡。 物件生命週期由長短:臨時物件;常駐記憶體;有的與其他物件存在複雜的依賴關係;狀態變化時必須滿足一些不變數的約束條件。 如何管理這些物件提出挑戰!處理不好會偏離MDD的方向。

  在生命週期中維護物件的完整性。避免模型由於管理生命週期的複雜性而陷入困境。有 三個模式來處理: 聚合(Aggregate):定義清晰的所有權和邊界使模型更加緊湊,避免出現盤根錯節的物件關係網;工廠(Factory)和組合(Respository)。

  當一個物件生命週期之始,使用工廠和組合提供了訪問和控制模型物件的方法,完善了MDD。 建立聚合的模型,並且把工廠和組合加入設計中來,可以使我們系統地對模型物件進行管理。 聚合圈出一個範偉,在這個範圍中,物件無論在哪個生命週期,保持不變性。

  在JiveJdon3.0中,值物件ForumState是被聚合在實體模型Forum中,Forum作為ForumState的一個根,由於它們資料必須保持一致性,不變數(invariant)是指無論何時發生資料變化必須滿足一致性規則,由於根控制了訪問,就無法繞過它修改內部元素,例如,如果沒有Forum實體物件這個根,就無法去修改物件狀態ForumState,ForumState獲得是通過Forum的getter方法獲得的。

  ForumState和Forum的分離有可以使修改論壇狀態資料(當發一個新帖時,必須更新當前論壇的最新帖子為該新帖),不會影響到Forum其他元素,特別是使用事務鎖定時,不必鎖住整個物件,見”領域驅動設計”書籍P92。

  另外,ForumThread和ForumMessage的關聯關係必設定成單向的,而不是雙向的,因為領域建模中,關聯越簡單越好。

  在JiveJdon3.0中,你可能注意到有一個com.jdon.jivejdon.service.factory.ForumBuilder,所有實體模型物件的獲得都是從這個工廠建立出來的,我曾經徘徊過:這個工廠類是否應該屬於持久層,因為JiveJdon3.0持久層沒有使用Hibernate這樣O/R Mapping框架,而是直接使用SQL,但是從持久層輸出的都是物件,這是必須堅持的一個設計原則(好像是MF的一個什麼後設資料模式) 。

  但是,Eric明確告訴我們,領域模型的工廠屬於應用層,頁就是還是應該處於業務層的,這樣好處很多,業務層設計根本無需從Hibernate等持久層框架獲得,而是從自己的工廠獲得。

  組合(Respository)又被翻譯成倉儲,我認為組合合適,主要用來返回一批物件,查詢組合常用來返回批量查詢結果,JdonFramework兩個快速開發支援:批量查詢其實應該是Respository的實現,實際也是過去Master-details的一種查詢實現。

  以com.jdon.jivejdon.presentation.action.ThreadListAction為例子,其功能是查詢論壇Forum下所有主題ForumThread,並分頁顯示,實現效果按這裡,我們在customizeListForm方法中將根Model
Forum設定進入,在threadList.jsp中,我們使用struts的標籤庫logic:iterator來遍歷組合物件threadListForm中的ForumThread集合。

JiveJdon中聚合根和邊界

ddd

失血模型

  MF(Martin Fowler)曾經提出有名的貧血模型或失血模型,讓我們好生迷惑和彷徨,他認為實體模型物件中只有弱行為setter和getter方法,沒有真正行為,好像缺少血液的人,不和諧了,不少高手又被忽悠了,大談貧血模型。

  其實,Eric已經認為,在DDD中,領域中一些概念不能作為模型中的物件來處理的,如果將這些功能概念強行加給實體物件和值物件,破壞模型中物件的定義,人為新增沒有意義的物件。服務是描述領域概念最自然的方式。

  為了在這些大師之間取得一個平衡,有人將Model的持久化操作(CRUD行為)整入到領域模型中,這是不是違背當初Dao模式初衷,Dao模式其實是橋模式和介面卡模式組合(見SUN的J2EE核心模式)。

  無論如何,我們的DDD專案中都是以失血模型存在著,所以,Eric呼喚:建模專家必須懂得實現,懂得軟體技術,MF可能會聽進去的。

DDD DCI和CQRS技術PPT介紹

Evans DDD書籍中Cargo貨物案例免費下載

相關文章

不變性immutablity設計

DDD DCI和領域事件

無堵塞的併發程式設計

MDD模型驅動快速開發

如何打敗CAP定理?

SpeedVan認為的VO

Domain Events非同步應用

在DDD分析設計下的JiveJdon3全新架構文件和原始碼

JiveJdon DDD設計討論

狀態物件:資料庫的替代者

領域模型驅動設計(DDD)之模型提煉

Organizing Domain Logic

貧血和充血模型的比較之我見

關於DDD的失血模型疑惑

跨越分析與設計的鴻溝

對領域驅動的一些綜合考慮

對值物件的理解

我理解的聚合,關聯,組合區別

DDD書中案例原始碼

更多建模案例專題討論

更多DDD專題討論

更多實體專題

更多值物件專題

更多服務專題

更多關於DDD討論