軟件系統面向對象的設計思想可謂歷史悠久,20世紀70年代的Smalltalk能夠說是面嚮對象語言的經典,直到今天咱們依然將這門語言視爲面嚮對象語言的基礎。隨着編程語言和技術的發展,各類語言特性層出不窮,面向對象是大部分語言的一個基本特性,像C++、Java、C#這樣的靜態語言,Ruby、Python這樣的動態語言都是面向對象的語言。web
可是面嚮對象語言並非銀彈,若是開發人員認爲使用面嚮對象語言寫出來的程度自己就是面向對象的,那就大錯特錯了,實際開發中,大量的業務邏輯堆積在一個巨型類中的例子家常便飯,代碼的複用性和擴展性沒法獲得保證。爲了解決這樣的問題,領域驅動設計提出了清晰的分層架構和領域對象的概念,讓面向對象的分析和設計進入了一個新的階段,對企業級軟件開發起到了巨大的推進做用。數據庫
本文主要介紹了領域驅動設計的基本概念、要素、特色,對比了事務腳本和領域模型的特色,最後介紹了咱們在軟件開發過程當中的領域驅動設計實踐。編程
2004年著名建模專家Eric Evans發表了他最具影響力的書籍:《Domain-Driven Design –Tackling Complexity in the Heart of Software》(中文譯名:領域驅動設計—軟件核心複雜性應對之道),書中提出了「領域驅動設計(簡稱 DDD)」的概念。設計模式
領域驅動設計事實上是針對OOAD的一個擴展和延伸,DDD基於面向對象分析與設計技術,對技術架構進行了分層規劃,同時對每一個類進行了策略和類型的劃分。架構
領域模型是領域驅動的核心。採用DDD的設計思想,業務邏輯再也不集中在幾個大型的類上,而是由大量相對小的領域對象(類)組成,這些類具有本身的狀態和行爲,每一個類是相對完整的獨立體,並與現實領域的業務對象映射。領域模型就是由這樣許多的細粒度的類組成。基於領域驅動的設計,保證了系統的可維護性、擴展性和複用性,在處理複雜業務邏輯方面有着先天的優點。app
領域驅動的核心應用場景就是解決複雜業務的設計問題,其特色與這一核心主題息息相關:框架
面對複雜的業務場景和需求,若是沒有創建和實現領域模型,會致使應用架構出現「胖服務層」和「貧血的領域模型」,在這樣的架構中,Service層開始積聚愈來愈多的業務邏輯,領域對象則成爲只有getter和setter方法的數據載體。這種作法還會致使領域特定業務邏輯和規則散佈於多個的Service類中,有些狀況下還會出現重複的邏輯。咱們曾經見過5000多行的Service類,上百個方法,代碼基本上是不可讀的。編程語言
在大多數狀況下,貧血的領域模型沒有成本效益。它們不會給公司帶來超越其它公司的競爭優點,由於在這種架構裏要實現業務需求變動,開發並部署到生產環境中去要花費太長的時間。性能
下面咱們簡單介紹一下領域驅動設計的分層架構和構成要素,這部份內容在Eric Evans的書中有很是詳盡的描述,想要詳細瞭解的,最好去讀原版書籍。學習
下面這張圖是該書中著名的分層架構圖,以下:
整個架構分爲四層,其核心就是領域層(Domain),全部的業務邏輯應該在領域層實現,具體描述以下:
用戶界面/展示層 |
負責向用戶展示信息以及解釋用戶命令。 |
應用層 |
很薄的一層,用來協調應用的活動。它不 包含業務邏輯。它不保留業務對象的狀態, 但它保有應用任務的進度狀態。 |
領域層 |
本層包含關於領域的信息。這是業務軟件 的核心所在。在這裏保留業務對象的狀態, 對業務對象和它們狀態的持久化被委託給 了基礎設施層。 |
基礎設施層 |
本層做爲其餘層的支撐庫存在。它提供了 層間的通訊,實現對業務對象的持久化, 包含對用戶界面層的支撐庫等做用。 |
領域驅動設計除了對系統架構進行了分層描述,還對對象(Object)作了明確的職責和策略劃分:
固然,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)負責處理某個相對內聚的業務邏輯單元,同時對內對外提供本地或遠程的服務。
下面是對各層的簡要描述:
另外,咱們引入了Spring的IOC容器,系統的控制層、領域層和持久化層元素都有IOC容器統一管理,實現徹底的接口分離和解耦。同時在控制、領域和持久化層均可以引用日誌服務。
咱們對領域驅動要素的定義上和原有的命名和含義上稍有區別。
原來的服務(Service),咱們定義爲業務服務(BusinessService),面向業務服務的架構是GAP平臺的核心設計思想,一個業務服務能夠由一個或多個領域模型和數據訪問對象(DAO)組成,去實現一個完整的業務邏輯單元。業務服務主要負責事務處理和維護各個領域對象之間的關係,同時爲上層訪問提供本地和遠程服務,服務類型包括Web Service,RMI等。
領域對象由實體(Entity)和值對象(VO)構成,實體類具有本身的屬性和行爲、狀態,能夠聚合VO,實體類之間能夠有聚合關聯等關係,能夠由數據訪問對象(DAO)進行持久化。
持久化由數據訪問對象(DAO)實現,不處理業務邏輯,主要負責實體類的持久化。提供多種持久化方式(O/R Mapping和JDBC)。
那麼如何在去實現領域驅動設計呢?咱們總結了如下四個步驟:
爲了更好的理解領域驅動設計,咱們基於以上設計方法,實現了一套簡單的網上書店系統。
網上書店系統是採用DDD設計思想構建的一個應用系統示例。經過網上書店系統,能夠快速理解領域驅動設計。該系統實現網上書店的經常使用功能:包括瀏覽書籍、挑選書籍、提交訂單、查看訂單、自動折扣、處理訂單、取消訂單等。未登陸用戶能夠瀏覽和挑選書籍;已登陸用戶能夠提交和查看本身相關的訂單;管理員能夠處理訂單。
通過業務抽象,即便是這樣一個簡單的業務場景也包含了不少領域對象,例如訂單、帳戶、書籍、購物車、購物項、折扣等,經過分析和設計,咱們能夠獲得這樣的設計圖(爲了查看方便,圖中的類隱藏了屬性信息):
BookStoreAction負責處理展示層的請求,並把請求轉發給業務服務IBookStoreBS,業務服務負責調度上圖中顯示的領域對象,處理該場景的全部業務。
其中領域對象和現實業務的對應關係爲:
與事務腳本的編程模式不一樣,領域驅動設計不是把業務邏輯放在BS(BusinessService)中,而是由具有屬性、行爲和狀態的領域對象處理。例如Order類,若是是貧血的POJO,那它內部只有與數據表字段對應的屬性以及getter和setter方法,而在領域驅動設計中,則是一個相對獨立的、可以處理自身關聯業務的領域對象。在本系統中,咱們對Order的描述以下:
訂單的實現類是gap.template.bookstore.model.Order,類中除了聯繫方式、郵寄地址等基本屬性外,還有如下領域相關的行爲:
經過以上的描述,咱們能夠看到,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出版社