領域驅動設計和實踐

作者: 池建強  發布時間: 2012-01-21 21:04  閱讀: 5471 次  推薦: 4   原文鏈接   [收藏]  

  軟件系統面向對象的設計思想可謂歷史悠久,20世紀70年代的Smalltalk可以說是面向對象語言的經典,直到今天我們依然將這門語言視為面向對象語言的基礎。隨著編程語言和技術的發展,各種語言特性層出不窮,面向對象是大部分語言的一個基本特性,像C++、Java、C#這樣的靜態語言,Ruby、Python這樣的動態語言都是面向對象的語言。

  但是面向對象語言并不是銀彈,如果開發人員認為使用面向對象語言寫出來的程序本身就是面向對象的,那就大錯特錯了,實際開發中,大量的業務邏輯堆積在一個巨型類中的例子屢見不鮮,代碼的復用性和擴展性無法得到保證。為了解決這樣的問題,領域驅動設計提出了清晰的分層架構和領域對象的概念,讓面向對象的分析和設計進入了一個新的階段,對企業級軟件開發起到了巨大的推動作用。

  本文主要介紹了領域驅動設計的基本概念、要素、特點,對比了事務腳本和領域模型的特點,最后介紹了我們在軟件開發過程中的領域驅動設計實踐。

  什么是領域驅動設計(DDD)

  2004年著名建模專家Eric Evans發表了他最具影響力的書籍:《Domain-Driven Design –Tackling Complexity in the Heart of Software》(中文譯名:領域驅動設計—軟件核心復雜性應對之道),書中提出了“領域驅動設計(簡稱 DDD)”的概念。

  領域驅動設計事實上是針對OOAD的一個擴展和延伸,DDD基于面向對象分析與設計技術,對技術架構進行了分層規劃,同時對每個類進行了策略和類型的劃分。

  領域模型是領域驅動的核心。采用DDD的設計思想,業務邏輯不再集中在幾個大型的類上,而是由大量相對小的領域對象(類)組成,這些類具備自己的狀態和行為,每個類是相對完整的獨立體,并與現實領域的業務對象映射。領域模型就是由這樣許多的細粒度的類組成。基于領域驅動的設計,保證了系統的可維護性、擴展性和復用性,在處理復雜業務邏輯方面有著先天的優勢。

  領域驅動設計的特點

  領域驅動的核心應用場景就是解決復雜業務的設計問題,其特點與這一核心主題息息相關:

  1. 分層架構與職責劃分:領域驅動設計很好的遵循了關注點分離的原則,提出了成熟、清晰的分層架構。同時對領域對象進行了明確的策略和職責劃分,讓領域對象和現實世界中的業務形成良好的映射關系,為領域專家與開發人員搭建了溝通的橋梁。
  2. 復用:在領域驅動設計中,領域對象是核心,每個領域對象都是一個相對完整的內聚的業務對象描述,所以可以形成直接的復用。同時設計過程是基于領域對象而不是基于數據庫的Schema,所以整個設計也是可以復用的。
  3. 使用場景:適合具備復雜業務邏輯的軟件系統,對軟件的可維護性和擴展性要求比較高。不適用簡單的增刪改查業務。

  如果不使用DDD?

  面對復雜的業務場景和需求,如果沒有建立和實現領域模型,會導致應用架構出現“胖服務層”和“貧血的領域模型”,在這樣的架構中,Service層開始積聚越來越多的業務邏輯,領域對象則成為只有getter和setter方法的數據載體。這種做法還會導致領域特定業務邏輯和規則散布于多個Service類中,有些情況下還會出現重復的邏輯。我們曾經見過5000多行的Service類,上百個方法,代碼基本上是不可讀的。

  在大多數情況下,貧血的領域模型沒有成本效益。它們不會給公司帶來超越其它公司的競爭優勢,因為在這種架構里要實現業務需求變更,開發并部署到生產環境中去要花費太長的時間。

  領域驅動設計的分層架構和構成要素

  下面我們簡單介紹一下領域驅動設計的分層架構和構成要素,這部分內容在Eric Evans的書中有非常詳盡的描述,想要詳細了解的,最好去讀原版書籍。

  下面這張圖是該書中著名的分層架構圖,如下:

  整個架構分為四層,其核心就是領域層(Domain),所有的業務邏輯應該在領域層實現,具體描述如下:

用戶界面/展現層

負責向用戶展現信息以及解釋用戶命令。

應用層 

很薄的一層,用來協調應用的活動。它不包含業務邏輯。它不保留業務對象的狀態,但它保有應用任務的進度狀態。

領域層 

本層包含關于領域的信息。這是業務軟件的核心所在。在這里保留業務對象的狀態,對業務對象和它們狀態的持久化被委托給了基礎設施層。

基礎設施層 

本層作為其他層的支撐庫存在。它提供了層間的通信,實現對業務對象的持久化,包含對用戶界面層的支撐庫等作用。

  領域驅動設計除了對系統架構進行了分層描述,還對對象(Object)做了明確的職責和策略劃分:

  1. 實體(Entities):具備唯一ID,能夠被持久化,具備業務邏輯,對應現實世界業務對象。
  2. 值對象(Value objects):不具有唯一ID,由對象的屬性描述,一般為內存中的臨時對象,可以用來傳遞參數或對實體進行補充描述。
  3. 工廠(Factories):主要用來創建實體,目前架構實踐中一般采用IOC容器來實現工廠的功能。
  4. 倉庫(Repositories):用來管理實體的集合,封裝持久化框架。
  5. 服務(Services):為上層建筑提供可操作的接口,負責對領域對象進行調度和封裝,同時可以對外提供各種形式的服務。

  當然,DDD中還提出了聚合和聚合根(Aggregate Root)的概念,不過我們在實踐過程發現聚合根有問題復雜化的傾向,用傳統的聚合、組合等概念去描述領域對象之間的關系更容易理解,所以這里對這個概念就不做介紹了。

  事務腳本和領域模型

  Martin Fowler 2004年所著的《企業應用架構模式(Patterns of Enterprise Application Architecture)》中的第九章領域邏輯模式(Domain Logic Patterns)專門介紹了事務腳本(Transaction Script)和領域模型(Domain Model),理解這兩種模式對設計和構建企業應用軟件非常有幫助,所以有必要介紹一下。

  事務腳本:

  事務腳本的核心是過程,通過過程的調用來組織業務邏輯,每個過程處理來自表現層的單個請求。大部分業務應用都可以被看成一系列事務,從某種程度上來 說,通過事務腳本處理業務,就像執行一條條Sql語句來實現數據庫信息的處理。事務腳本把業務邏輯組織成單個過程,在過程中直接調用數據庫,業務邏輯在服務(Service)層處理。

  事務腳本模式可以簡單的通過UML圖表示成這樣:

  由Action層處理UI層的動作請求,將Request中的數據組裝后傳遞給BusinessService,BS層做簡單的邏輯處理后,調用數據訪問對象進行數據持久化,其中VO充當了數據傳輸對象的作用,一般是貧血的POJO,只具備getter和setter方法,沒有狀態和行為。

  事務腳本模式的特點是簡單容易理解,面向過程設計。對于少量邏輯的業務應用來說,事務腳本模式簡單自然,性能良好,容易理解,而且一個事務的處理不會影響其他事務。不過缺點也很明顯,對于復雜的業務邏輯處理力不從心,難以保持良好的設計,事務之間的冗余代碼不斷增多,通過復制粘貼方式進行復用。可維護性和擴展性變差。

  領域模型:

  領域模型的特點也比較明顯, 屬于面向對象設計,領域模型具備自己的屬性行為狀態,并與現實世界的業務對象相映射。各類具備明確的職責劃分,領域對象元素之間通過聚合和引用等關系配合解決實際業務應用和規則。可復用,可維護,易擴展,可以采用合適的設計模型進行詳細設計。缺點是相對復雜,要求設計人員有良好的抽象能力。

  領域模型對應的就是領域驅動設計中劃分的領域層,這里就不詳細討論了。

  在實際的設計中,我們需要根據具體的需求選擇相應的設計模式。具備復雜業務邏輯的核心業務系統適合使用領域模型,簡單的信息管理系統可以考慮采用事務腳本模式。

  領域驅動設計實踐

  下面主要講一下我們在構建企業級應用開發平臺中對DDD的實踐和擴展。

  本人近年來一直在從事企業級應用開發平臺的相關工作,GAP平臺是我們的一個軟件產品,用來解決企業級軟件開發過程中復用、快速開發和過程規范等問 題。設計這樣一個平臺,從底層的框架上就應該能夠支撐復雜業務邏輯的系統構建,所以我們在大的架構設計思路上采用了領域驅動設計的思路,并根據實際采用的技術和要實現的功能對DDD的四層架構進行了細化和實現:

  整個平臺采用了JavaEE的技術及其相關的開源框架。系統的核心業務邏輯由Domain層處理,其中的業務服務(BusinessService)負責處理某個相對內聚的業務邏輯單元,同時對內對外提供本地或遠程的服務。

  下面是對各層的簡要描述:

  1. View:展示層,由于GAP平臺主要面向B/S架構,展示層主要由web資源文件組成,包括JSP,JS和大量的界面控件,同時還采用了AJAX和Flex等RIA技術,負責向用戶展現豐富的界面信息,并執行用戶的命令。
  2. Control:控制層,負責展示層請求的轉發、調度和基礎驗證,同時自動攔截后臺返回的Runtime異常信息,如果控制層需要與第三方系統交互,可以通過Action做遠程的請求。
  3. Domain:領域層,是系統最為豐富的一層,主要負責處理整個系統的業務邏輯。這一層包括業務服務和領域對象,同時負責系統的事務管理。其中業務服務可以提供本地調用和共享遠程服務的功能。
  4. Persistence:持久化層,主要負責數據持久化,支持O/R Mapping和JDBC。對數據源的訪問提供多種方式。

  另外,我們引入了Spring的IOC容器,系統的控制層、領域層和持久化層元素都有IOC容器統一管理,實現完全的接口分離和解耦。同時在控制、領域和持久化層都可以引用日志服務。

  我們對領域驅動要素的定義上和原有的命名和含義上稍有區別。

  原來的服務(Service),我們定義為業務服務(BusinessService),面向業務服務的架構是GAP平臺的核心設計思想,一個業務服務可以由一個或多個領域模型和數據訪問對象(DAO)組成,去實現一個完整的業務邏輯單元。業務服務主要負責事務處理和維護各個領域對象之間的關系,同時為上層訪問提供本地和遠程服務,服務類型包括Web Service,RMI等。

  領域對象由實體(Entity)和值對象(VO)構成,實體類具備自己的屬性和行為、狀態,可以聚合VO,實體類之間可以有聚合、關聯等關系,可以由數據訪問對象(DAO)進行持久化。

  持久化由數據訪問對象(DAO)實現,不處理業務邏輯,主要負責實體類的持久化。提供多種持久化方式(O/R Mapping和JDBC)。

  那么如何在去實現領域驅動設計呢?我們總結了以下四個步驟:

  1. 確定業務服務(Business Service):根據業務需求和功能模塊劃分,確定業務單元,每個Business Service是一個內聚的業務單元,覆蓋相關的領域對象。
  2. 定義領域對象(Entity, VO):根據業務單元的業務邏輯定義領域對象,通過UML方法和設計模式描述領域對象。
  3. 定義領域對象的屬性和關聯關系:確定領域對象的各種屬性和各個領域對象之間的關聯關系。
  4. 為領域對象增加行為:根據業務需求(系統用例和界面原型等)為領域對象增加行為,并定義哪些方法要被業務服務引用。

  案例——網上書店

  為了更好的理解領域驅動設計,我們基于以上設計方法,實現了一套簡單的網上書店系統。

  網上書店系統是采用DDD設計思想構建的一個應用系統示例。通過網上書店系統,可以快速理解領域驅動設計。該系統實現網上書店的常用功能:包括瀏覽書籍、挑選書籍、提交訂單、查看訂單、自動折扣、處理訂單、取消訂單等。未登錄用戶可以瀏覽和挑選書籍;已登錄用戶可以提交和查看自己相關的訂單;管理員可以處理訂單。

  經過業務抽象,即使是這樣一個簡單的業務場景也包含了很多領域對象,例如訂單、賬戶、書籍、購物車、購物項、折扣等,通過分析和設計,我們可以得到這樣的設計圖(為了查看方便,圖中的類隱藏了屬性信息):

  BookStoreAction負責處理展現層的請求,并把請求轉發給業務服務IBookStoreBS,業務服務負責調度上圖中顯示的領域對象,處理該場景的所有業務。

  其中領域對象和現實業務的對應關系為:

  • Account——賬戶
  • Order——訂單
  • Book——書籍
  • Cart——購物車
  • Item——訂單項
  • Discount——折扣

  與事務腳本的編程模式不同,領域驅動設計不是把業務邏輯放在BS(BusinessService)中,而是由具備屬性、行為和狀態的領域對象處理。例如Order類,如果是貧血的POJO,那它內部只有與數據表字段對應的屬性以及getter和setter方法,而在領域驅動設計中,則是一個相對獨立的、能夠處理自身關聯業務的領域對象。在本系統中,我們對Order的描述如下:

  訂單的實現類是gap.template.bookstore.model.Order,類中除了聯系方式、郵寄地址等基本屬性外,還有以下領域相關的行為:

  1. init(...),結算時調用方法,根據當前用戶與購物車中的Items初始化訂單,供用戶修改。
  2. submit(...),提交訂單時調用的方法,保存訂單。
  3. cancel(...),取消訂單,把訂單和相關item的狀態設置為“已取消”,然后委托Dao進行持久化。
  4. dispose(...),處理訂單,首先更新訂單項的狀態,然后委托Dao持久化訂單數據。
  5. reSubmit、setItemsStatus......

  通過以上的描述,我們可以看到,Order類基本上覆蓋了現實世界中訂單這個業務的所有行為和狀態,是相對內聚的,這樣的特性使其復用性大大增加, 即使未來開發新的模塊,涉及到訂單業務的,可以直接復用Order類。同時在后期維護中,如果我想了解訂單的業務,直接讀Order的代碼就可以了。

  從上圖中我們還可以清晰的看到各個領域對象之間的關系。Order和Cart都聚合了Item,對應都是1...n,Item聚合了Book,對應關系1...1。Order分別與折扣、賬戶發生關聯和調用等等,整個網上書店的場景就這樣描述出來了。

  另外,不要忘了BS,除了起到基礎設施的作用外(事務管理和服務共享),它還要負責調度和維護領域對象之間的關系。因為總會有些業務邏輯,既不屬于這個領域對象,也不屬于那個,那這部分業務由誰來處理呢?由BS來處理。例如在管理員處理訂單這個場景中,首先需要根據訂單信息獲取賬戶,根據賬戶信息確定折扣率,同時進行余額校驗,如果校驗通過,就會調用訂單對象的dispose方法處理訂單,這個場景會涉及到Order、Account、 Discount等對象,這樣的業務邏輯,應該由BS實現。

  IBookStoreDao是數據訪問對象,可以被BS調用,用來持久化對象,也可以被領域對象引用,用來持久化自身。

  通過以上的描述,我們可以看到,整個設計和實現是優雅、清晰的。業務邏輯沒有堆積在BS中,而是分散在BS和各個領域對象中,服務和對象都與現實世界的業務息息相關,無論是對領域專家、開發人員和后期維護人員,都能這種方式中獲得自己需要的內容。

  總結

  我們采用領域驅動設計相對比較早,就我個人的檢驗和實踐而言,DDD對構建企業級應用開發平臺和大型核心業務系統的作用是非常明顯的,無論是在產品的穩定性、擴展性、可維護性、生命周期等方面都有顯著的提升。

  但是,由于這樣那樣的原因(復雜度、工期、開發人員能力限制等等),很多人會不自覺的抵制采用DDD,有時候一個軟件項目重寫了兩次,第二次依然不去做良好的設計。事實上采用了DDD的設計方法,我們的設計階段已經變得非常輕量級和敏捷了,開發人員只要能夠把領域模型之間的關系畫出來并描述說明,并與需求人員達成一致,那么做出來的東西基本上是靠譜的。

  在技術領域,只有主動的嘗試和提升,效果才是最明顯的。很多人問過我,如何開始學習和實踐XXX,其實很簡單,現在就開始吧!

  參考資料

  《 領域驅動設計—軟件核心復雜性應對之道》,Evans Eric著,Addison-Wesley出版社

  《企業應用架構模式》, Martin Fowler著, Addison-Wesley出版社

4
0
 
 
 

文章列表

arrow
arrow
    全站熱搜

    大師兄 發表在 痞客邦 留言(0) 人氣()