軟件系統(tǒng)面向對象的設計思想可謂歷史悠久英融,20世紀70年代的Smalltalk可以說是面向對象語言的經典丙者,直到今天我們依然將這門語言視為面向對象語言的基礎翠订。隨著編程語言和技術的發(fā)展峰伙,各種語言特性層出不窮浸卦,面向對象是大部分語言的一個基本特性缓醋,像C++剔交、Java、C#這樣的靜態(tài)語言改衩,Ruby岖常、 Python這樣的動態(tài)語言都是面向對象的語言。
但是面向對象語言并不是銀彈葫督,如果開發(fā)人員認為使用面向對象語言寫出來的程度本身就是面向對象的竭鞍,那就大錯特錯了板惑,實際開發(fā)中,大量的業(yè)務邏輯堆積 在一個巨型類中的例子屢見不鮮偎快,代碼的復用性和擴展性無法得到保證冯乘。為了解決這樣的問題,領域驅動設計提出了清晰的分層架構和領域對象的概念晒夹,讓面向對象 的分析和設計進入了一個新的階段裆馒,對企業(yè)級軟件開發(fā)起到了巨大的推動作用。
本文主要介紹了領域驅動設計的基本概念丐怯、要素喷好、特點,對比了事務腳本和領域模型的特點读跷,最后介紹了我們在軟件開發(fā)過程中的領域驅動設計實踐梗搅。
什么是領域驅動設計(DDD)
2004年著名建模專家Eric Evans發(fā)表了他最具影響力的書籍:《Domain-Driven Design –Tackling Complexity in the Heart of Software》(中文譯名:領域驅動設計—軟件核心復雜性應對之道),書中提出了“領域驅動設計(簡稱 DDD)”的概念效览。
領域驅動設計事實上是針對OOAD的一個擴展和延伸无切,DDD基于面向對象分析與設計技術,對技術架構進行了分層規(guī)劃丐枉,同時對每個類進行了策略和類型的劃分哆键。
領域模型是領域驅動的核心。采用DDD的設計思想瘦锹,業(yè)務邏輯不再集中在幾個大型的類上籍嘹,而是由大量相對小的領域對象(類)組成,這些類具備自己的狀態(tài)和行為沼本,每個類是相對完整的獨立體噩峦,并與現(xiàn)實領域的業(yè)務對象映射锭沟。領域模型就是由這樣許多的細粒度的類組成抽兆。基于領域驅動的設計族淮,保證了系統(tǒng)的可維護 性辫红、擴展性和復用性,在處理復雜業(yè)務邏輯方面有著先天的優(yōu)勢祝辣。
領域驅動設計的特點
領域驅動的核心應用場景就是解決復雜業(yè)務的設計問題贴妻,其特點與這一核心主題息息相關:
- 分層架構與職責劃分:領域驅動設計很好的遵循了關注點分離的原則,提出了成熟蝙斜、清晰的分層架構名惩。同時對領域對象進行了明確的策略和職責劃分,讓領域對象和現(xiàn)實世界中的業(yè)務形成良好的映射關系孕荠,為領域專家與開發(fā)人員搭建了溝通的橋梁娩鹉。
- 復用:在領域驅動設計中攻谁,領域對象是核心,每個領域對象都是一個相對完整的內聚的業(yè)務對象描述弯予,所以可以形成直接的復用戚宦。同時設計過程是基于領域對象而不是基于數(shù)據庫的Schema,所以整個設計也是可以復用的锈嫩。
- 使用場景:適合具備復雜業(yè)務邏輯的軟件系統(tǒng)受楼,對軟件的可維護性和擴展性要求比較高。不適用簡單的增刪改查業(yè)務呼寸。
如果不使用DDD艳汽?
面對復雜的業(yè)務場景和需求,如果沒有建立和實現(xiàn)領域模型等舔,會導致應用架構出現(xiàn)“胖服務層”和“貧血的領域模型”骚灸,在這樣的架構中,Service層 開始積聚越來越多的業(yè)務邏輯慌植,領域對象則成為只有getter和setter方法的數(shù)據載體甚牲。這種做法還會導致領域特定業(yè)務邏輯和規(guī)則散布于多個的 Service類中,有些情況下還會出現(xiàn)重復的邏輯蝶柿。我們曾經見過5000多行的Service類丈钙,上百個方法,代碼基本上是不可讀的交汤。
在大多數(shù)情況下雏赦,貧血的領域模型沒有成本效益。它們不會給公司帶來超越其它公司的競爭優(yōu)勢芙扎,因為在這種架構里要實現(xiàn)業(yè)務需求變更星岗,開發(fā)并部署到生產環(huán)境中去要花費太長的時間。
領域驅動設計的分層架構和構成要素
下面我們簡單介紹一下領域驅動設計的分層架構和構成要素戒洼,這部分內容在Eric Evans的書中有非常詳盡的描述俏橘,想要詳細了解的,最好去讀原版書籍圈浇。
下面這張圖是該書中著名的分層架構圖寥掐,如下:
整個架構分為四層,其核心就是領域層(Domain)磷蜀,所有的業(yè)務邏輯應該在領域層實現(xiàn)召耘,具體描述如下:
分層 | 具體描述 |
---|---|
用戶界面/展現(xiàn)層 | 負責向用戶展現(xiàn)信息以及解釋用戶命令。 |
應用層 ? | 很薄的一層,用來協(xié)調應用的活動褐隆。它不包含業(yè)務邏輯污它。它不保留業(yè)務對象的狀態(tài),但它保有應用任務的進度狀態(tài)。 |
領域層 | 本層包含關于領域的信息。這是業(yè)務軟件的核心所在衫贬。在這里保留業(yè)務對象的狀態(tài),對業(yè)務對象和它們狀態(tài)的持久化被委托給了基礎設施層蜜宪。 |
基礎設施層 | 本層作為其他層的支撐庫存在。它提供了層間的通信,實現(xiàn)對業(yè)務對象的持久化,包含對用戶界面層的支撐庫等作用祥山。 |
領域驅動設計除了對系統(tǒng)架構進行了分層描述圃验,還對對象(Object)做了明確的職責和策略劃分:
- 實體(Entities):具備唯一ID,能夠被持久化缝呕,具備業(yè)務邏輯澳窑,對應現(xiàn)實世界業(yè)務對象。
- 值對象(Value objects):不具有唯一ID供常,由對象的屬性描述摊聋,一般為內存中的臨時對象,可以用來傳遞參數(shù)或對實體進行補充描述栈暇。
- 工廠(Factories):主要用來創(chuàng)建實體麻裁,目前架構實踐中一般采用IOC容器來實現(xiàn)工廠的功能。
- 倉庫(Repositories):用來管理實體的集合源祈,封裝持久化框架煎源。
- 服務(Services):為上層建筑提供可操作的接口,負責對領域對象進行調度和封裝香缺,同時可以對外提供各種形式的服務手销。
當然,DDD中還提出了聚合和聚合根(Aggregate Root)的概念图张,不過我們在實踐過程發(fā)現(xiàn)聚合根有問題復雜化的傾向锋拖,用傳統(tǒng)的聚合、組合等概念去描述領域對象之間的關系更容易理解祸轮,所以這里對這個概念就不做介紹了兽埃。
事務腳本和領域模型
Martin Fowler 2004年所著的企業(yè)應用架構模式(Patterns of Enterprise Application Architecture)中的第九章領域邏輯模式(Domain Logic Patterns)專門介紹了事務腳本(Transaction Script)和領域模型(Domain Model),理解這兩種模式對設計和構建企業(yè)應用軟件非常有幫助适袜,所以有必要介紹一下柄错。
事務腳本:
事務腳本的核心是過程,通過過程的調用來組織業(yè)務邏輯痪蝇,每個過程處理來自表現(xiàn)層的單個請求鄙陡。大部分業(yè)務應用都可以被看成一系列事務冕房,從某種程度上來 說躏啰,通過事務腳本處理業(yè)務,就像執(zhí)行一條條Sql語句來實現(xiàn)數(shù)據庫信息的處理耙册。事務腳本把業(yè)務邏輯組織成單個過程给僵,在過程中直接調用數(shù)據庫,業(yè)務邏輯在服 務(Service)層處理。
事務腳本模式可以簡單的通過UML圖表示成這樣:
由Action層處理UI層的動作請求帝际,將Request中的數(shù)據組裝后傳遞給BusinessService蔓同,BS層做簡單的邏輯處理后,調用數(shù) 據訪問對象進行數(shù)據持久化蹲诀,其中VO充當了數(shù)據傳輸對象的作用斑粱,一般是貧血的POJO,只具備getter和setter方法脯爪,沒有狀態(tài)和行為则北。
事務腳本模式的特點是簡單容易理解,面向過程設計痕慢。對于少量邏輯的業(yè)務應用來說尚揣,事務腳本模式簡單自然,性能良好掖举,容易理解快骗,而且一個事務的處理不 會影響其他事務。不過缺點也很明顯塔次,對于復雜的業(yè)務邏輯處理力不從心方篮,難以保持良好的設計,事務之間的冗余代碼不斷增多励负,通過復制粘貼方式進行復用恭取。可維 護性和擴展性變差熄守。
領域模型:
領域模型的特點也比較明顯蜈垮, 屬于面向對象設計,領域模型具備自己的屬性行為狀態(tài)裕照,并與現(xiàn)實世界的業(yè)務對象相映射攒发。各類具備明確的職責劃分,領域對象元素之間通過聚合和引用等關系配合 解決實際業(yè)務應用和規(guī)則晋南』菰常可復用,可維護负间,易擴展偶妖,可以采用合適的設計模型進行詳細設計。缺點是相對復雜政溃,要求設計人員有良好的抽象能力趾访。
領域模型對應的就是領域驅動設計中劃分的領域層,這里就不詳細討論了董虱。
在實際的設計中扼鞋,我們需要根據具體的需求選擇相應的設計模式申鱼。具備復雜業(yè)務邏輯的核心業(yè)務系統(tǒng)適合使用領域模型,簡單的信息管理系統(tǒng)可以考慮采用事務腳本模式云头。
領域驅動設計實踐
下面主要講一下我們在構建企業(yè)級應用開發(fā)平臺中對DDD的實踐和擴展捐友。
本人近年來一直在從事企業(yè)級應用開發(fā)平臺的相關工作,GAP平臺是我們的一個軟件產品溃槐,用來解決企業(yè)級軟件開發(fā)過程中復用匣砖、快速開發(fā)和過程規(guī)范等問 題。設計這樣一個平臺昏滴,從底層的框架上就應該能夠支撐復雜業(yè)務邏輯的系統(tǒng)構建脆粥,所以我們在大的架構設計思路上采用了領域驅動設計的思路,并根據實際采用的技術和要實現(xiàn)的功能對DDD的四層架構進行了細化和實現(xiàn):
整個平臺采用了JavaEE的技術及其相關的開源框架影涉。系統(tǒng)的核心業(yè)務邏輯由Domain層處理变隔,其中的業(yè)務服務(BusinessService)負責處理某個相對內聚的業(yè)務邏輯單元,同時對內對外提供本地或遠程的服務蟹倾。
下面是對各層的簡要描述:
- View:展示層匣缘,由于GAP平臺主要面向B/S架構,展示層主要由web資源文件組成鲜棠,包括JSP肌厨,JS和大量的界面控件,同時還采用了AJAX和Flex等RIA技術豁陆,負責向用戶展現(xiàn)豐富的界面信息柑爸,并執(zhí)行用戶的命令。
- Control:控制層盒音,負責展示層請求的轉發(fā)表鳍、調度和基礎驗證,同時自動攔截后臺返回的Runtime異常信息祥诽,如果控制層需要與第三方系統(tǒng)交互譬圣,可以通過Action做遠程的請求。
- Domain:領域層雄坪,是系統(tǒng)最為豐富的一層厘熟,主要負責處理整個系統(tǒng)的業(yè)務邏輯。這一層包括業(yè)務服務和領域對象维哈,同時負責系統(tǒng)的事務管理绳姨。其中業(yè)務服務可以提供本地調用和共享遠程服務的功能。
- Persistence:持久化層阔挠,主要負責數(shù)據持久化飘庄,支持O/R Mapping和JDBC。對數(shù)據源的訪問提供多種方式谒亦。
另外,我們引入了Spring的IOC容器竭宰,系統(tǒng)的控制層、領域層和持久化層元素都有IOC容器統(tǒng)一管理份招,實現(xiàn)完全的接口分離和解耦切揭。同時在控制、領域和持久化層都可以引用日志服務锁摔。
我們對領域驅動要素的定義上和原有的命名和含義上稍有區(qū)別廓旬。
原來的服務(Service),我們定義為業(yè)務服務(BusinessService)谐腰,面向業(yè)務服務的架構是GAP平臺的核心設計思想孕豹,一個業(yè)務服務可以由一個或多個領域模型和數(shù)據訪問對象(DAO)組成,去實現(xiàn)一個完整的業(yè)務邏輯單元十气。業(yè)務服務主要負責事務處理和維護各個領域對象之間的關系励背,同 時為上層訪問提供本地和遠程服務,服務類型包括Web Service砸西,RMI等叶眉。
領域對象由實體(Entity)和值對象(VO)構成,實體類具備自己的屬性和行為芹枷、狀態(tài)衅疙,可以聚合VO,實體類之間可以有聚合關聯(lián)等關系鸳慈,可以由數(shù)據訪問對象(DAO)進行持久化饱溢。
持久化由數(shù)據訪問對象(DAO)實現(xiàn),不處理業(yè)務邏輯走芋,主要負責實體類的持久化绩郎。提供多種持久化方式(O/R Mapping和JDBC)。
那么如何在去實現(xiàn)領域驅動設計呢翁逞?我們總結了以下四個步驟:
- 確定業(yè)務服務(Business Service):根據業(yè)務需求和功能模塊劃分嗽上,確定業(yè)務單元,每個Business Service是一個內聚的業(yè)務單元熄攘,覆蓋相關的領域對象兽愤。
- 定義領域對象(Entity, VO):根據業(yè)務單元的業(yè)務邏輯定義領域對象挪圾,通過UML方法和設計模式描述領域對象浅萧。
- 定義領域對象的屬性和關聯(lián)關系:確定領域對象的各種屬性和各個領域對象之間的關聯(lián)關系。
- 為領域對象增加行為:根據業(yè)務需求(系統(tǒng)用例和界面原型等)為領域對象增加行為哲思,并定義哪些方法要被業(yè)務服務引用洼畅。
案例——網上書店
為了更好的理解領域驅動設計,我們基于以上設計方法棚赔,實現(xiàn)了一套簡單的網上書店系統(tǒng)帝簇。
網上書店系統(tǒng)是采用DDD設計思想構建的一個應用系統(tǒng)示例徘郭。通過網上書店系統(tǒng),可以快速理解領域驅動設計丧肴。該系統(tǒng)實現(xiàn)網上書店的常用功能:包括瀏覽書籍残揉、挑選書籍、提交訂單芋浮、查看訂單抱环、自動折扣、處理訂單纸巷、取消訂單等镇草。未登錄用戶可以瀏覽和挑選書籍;已登錄用戶可以提交和查看自己相關的訂單瘤旨;管理員 可以處理訂單梯啤。
經過業(yè)務抽象,即使是這樣一個簡單的業(yè)務場景也包含了很多領域對象存哲,例如訂單条辟、賬戶、書籍宏胯、購物車羽嫡、購物項、折扣等肩袍,通過分析和設計杭棵,我們可以得到這樣的設計圖(為了查看方便,圖中的類隱藏了屬性信息):
BookStoreAction負責處理展現(xiàn)層的請求氛赐,并把請求轉發(fā)給業(yè)務服務IBookStoreBS魂爪,業(yè)務服務負責調度上圖中顯示的領域對象,處理該場景的所有業(yè)務艰管。
其中領域對象和現(xiàn)實業(yè)務的對應關系為:
- Account——賬戶
- Order——訂單
- Book——書籍
- Cart——購物車
- Item——訂單項
- Discount——折扣
與事務腳本的編程模式不同滓侍,領域驅動設計不是把業(yè)務邏輯放在BS(BusinessService)中,而是由具備屬性牲芋、行為和狀態(tài)的領域對象處理撩笆。例如Order類,如果是貧血的POJO缸浦,那它內部只有與數(shù)據表字段對應的屬性以及getter和setter方法夕冲,而在領域驅動設計中,則是一個相對獨立的裂逐、能夠處理自身關聯(lián)業(yè)務的領域對象歹鱼。在本系統(tǒng)中,我們對Order的描述如下:
訂單的實現(xiàn)類是gap.template.bookstore.model.Order卜高,類中除了聯(lián)系方式弥姻、郵寄地址等基本屬性外南片,還有以下領域相關的行為:
- init(...),結算時調用方法庭敦,根據當前用戶與購物車中的Items初始化訂單疼进,供用戶修改。
- submit(...)螺捐,提交訂單時調用的方法颠悬,保存訂單矮燎。
- cancel(...)定血,取消訂單,把訂單和相關item的狀態(tài)設置為“已取消”诞外,然后委托Dao進行持久化澜沟。
- dispose(...),處理訂單峡谊,首先更新訂單項的狀態(tài)茫虽,然后委托Dao持久化訂單數(shù)據。
- reSubmit既们、setItemsStatus......
通過以上的描述濒析,我們可以看到,Order類基本上覆蓋了現(xiàn)實世界中訂單這個業(yè)務的所有行為和狀態(tài)啥纸,是相對內聚的号杏,這樣的特性使其復用性大大增加, 即使未來開發(fā)新的模塊斯棒,涉及到訂單業(yè)務的盾致,可以直接復用Order類。同時在后期維護中荣暮,如果我想了解訂單的業(yè)務庭惜,直接讀Order的代碼就可以了。
從上圖中我們還可以清晰的看到各個領域對象之間的關系穗酥。Order和Cart都聚合了Item护赊,對應都是1...n,Item聚合了Book砾跃,對應關系1...1百揭。Order分別與折扣、賬戶發(fā)生關聯(lián)和調用等等蜓席,整個網上書店的場景就這樣描述出來了器一。
另外,不要忘了BS厨内,除了起到基礎設施的作用外(事務管理和服務共享)祈秕,它還要負責調度和維護領域對象之間的關系渺贤。因為總會有些業(yè)務邏輯,既不屬于這個領域對象请毛,也不屬于那個志鞍,那這部分業(yè)務由誰來處理呢?由BS來處理方仿。例如在管理員處理訂單這個場景中固棚,首先需要根據訂單信息獲取賬戶,根據賬戶信息確 定折扣率仙蚜,同時進行余額校驗此洲,如果校驗通過,就會調用訂單對象的dispose方法處理訂單委粉,這個場景會涉及到Order呜师、Account、 Discount等對象贾节,這樣的業(yè)務邏輯汁汗,應該由BS實現(xiàn)。
IBookStoreDao是數(shù)據訪問對象栗涂,可以被BS調用知牌,用來持久化對象,也可以被領域對象引用斤程,用來持久化自身角寸。
通過以上的描述,我們可以看到暖释,整個設計和實現(xiàn)是優(yōu)雅袭厂、清晰的。業(yè)務邏輯沒有堆積在BS中球匕,而是分散在BS和各個領域對象中纹磺,服務和對象都與現(xiàn)實世界的業(yè)務息息相關,無論是對領域專家亮曹、開發(fā)人員和后期維護人員橄杨,都能這種方式中獲得自己需要的內容。
總結
我們采用領域驅動設計相對比較早照卦,就我個人的檢驗和實踐而言式矫,DDD對構建企業(yè)級應用開發(fā)平臺和大型核心業(yè)務系統(tǒng)的作用是非常明顯的,無論是在產品的穩(wěn)定性役耕、擴展性采转、可維護性、生命周期等方面都有顯著的提升。
但是故慈,由于這樣那樣的原因(復雜度板熊、工期、開發(fā)人員能力限制等等)察绷,很多人會不自覺的抵制采用DDD干签,有時候一個軟件項目重寫了兩次,第二次依然不 去做良好的設計拆撼。事實上采用了DDD的設計方法容劳,我們的設計階段已經變得非常輕量級和敏捷了,開發(fā)人員只要能夠把領域模型之間的關系畫出來并描述說明闸度,并與需求人員達成一致竭贩,那么做出來的東西基本上是靠譜的。
在技術領域筋岛,只有主動的嘗試和提升娶视,效果才是最明顯的晒哄。很多人問過我睁宰,如何開始學習和實踐XXX,其實很簡單寝凌,現(xiàn)在就開始吧柒傻!
參考資料
- 《 領域驅動設計—軟件核心復雜性應對之道》,Evans Eric著较木,Addison-Wesley出版社
- 《企業(yè)應用架構模式》红符, Martin Fowler著, Addison-Wesley出版社
- 初探領域驅動設計(1)為復雜業(yè)務而生
- 初探領域驅動設計(2)Repository在DDD中的應用