實戰DDD

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

板橋里人 http://www.jdon.com 2006/7/10(轉載請保留)

  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是告訴我們如何做好業務層!並以領域驅動設計思想來選擇和合適的框架,本文以基於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集合。

失血模型

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

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

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

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

發佈了2 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章