领域驱动设计和实践
引言
軟件系統(tǒng)面向?qū)ο蟮脑O(shè)計思想可謂歷史悠久,20世紀70年代的Smalltalk可以說是面向?qū)ο笳Z言的經(jīng)典,直到今天我們依然將這門語言視為面向?qū)ο笳Z言的基礎(chǔ)。隨著編程語言和技術(shù)的發(fā)展,各種語言特性層出不窮,面向?qū)ο笫谴蟛糠终Z言的一個基本特性,像C++、Java、C#這樣的靜態(tài)語言,Ruby、Python這樣的動態(tài)語言都是面向?qū)ο蟮恼Z言。
但是面向?qū)ο笳Z言并不是銀彈,如果開發(fā)人員認為使用面向?qū)ο笳Z言寫出來的程度本身就是面向?qū)ο蟮?#xff0c;那就大錯特錯了。實際開發(fā)中,大量的業(yè)務(wù)邏輯堆積在一個巨型類中的例子屢見不鮮,代碼的復(fù)用性和擴展性無法得到保證。為了解決這樣的問題,領(lǐng)域驅(qū)動設(shè)計提出了清晰的分層架構(gòu)和領(lǐng)域?qū)ο蟮母拍?#xff0c;讓面向?qū)ο蟮姆治龊驮O(shè)計進入了一個新的階段,對企業(yè)級軟件開發(fā)起到了巨大的推動作用。
本文主要介紹了領(lǐng)域驅(qū)動設(shè)計的基本概念、要素、特點,對比了事務(wù)腳本和領(lǐng)域模型的特點,最后介紹了我們在軟件開發(fā)過程中的領(lǐng)域驅(qū)動設(shè)計實踐。
什么是領(lǐng)域驅(qū)動設(shè)計(DDD)
2004年著名建模專家Eric Evans發(fā)表了他最具影響力的書籍:《Domain-Driven Design: Tackling Complexity in the Heart of Software》(中文譯名:領(lǐng)域驅(qū)動設(shè)計:軟件核心復(fù)雜性應(yīng)對之道),書中提出了領(lǐng)域驅(qū)動設(shè)計(簡稱 DDD)的概念。
領(lǐng)域驅(qū)動設(shè)計事實上是針對OOAD的一個擴展和延伸,DDD基于面向?qū)ο蠓治雠c設(shè)計技術(shù),對技術(shù)架構(gòu)進行了分層規(guī)劃,同時對每個類進行了策略和類型的劃分。
領(lǐng)域模型是領(lǐng)域驅(qū)動的核心。采用DDD的設(shè)計思想,業(yè)務(wù)邏輯不再集中在幾個大型的類上,而是由大量相對小的領(lǐng)域?qū)ο?類)組成,這些類具備自己的狀態(tài)和行為,每個類是相對完整的獨立體,并與現(xiàn)實領(lǐng)域的業(yè)務(wù)對象映射。領(lǐng)域模型就是由這樣許多的細粒度的類組成?;陬I(lǐng)域驅(qū)動的設(shè)計,保證了系統(tǒng)的可維護性、擴展性和復(fù)用性,在處理復(fù)雜業(yè)務(wù)邏輯方面有著先天的優(yōu)勢。
領(lǐng)域驅(qū)動設(shè)計的特點
領(lǐng)域驅(qū)動的核心應(yīng)用場景就是解決復(fù)雜業(yè)務(wù)的設(shè)計問題,其特點與這一核心主題息息相關(guān):
如果不使用DDD?
面對復(fù)雜的業(yè)務(wù)場景和需求,如果沒有建立和實現(xiàn)領(lǐng)域模型,會導(dǎo)致應(yīng)用架構(gòu)出現(xiàn)胖服務(wù)層和貧血的領(lǐng)域模型,在這樣的架構(gòu)中,Service層開始積聚越來越多的業(yè)務(wù)邏輯,領(lǐng)域?qū)ο髣t成為只有g(shù)etter和setter方法的數(shù)據(jù)載體。這種做法還會導(dǎo)致領(lǐng)域特定業(yè)務(wù)邏輯和規(guī)則散布于多個的Service類中,有些情況下還會出現(xiàn)重復(fù)的邏輯。我們曾經(jīng)見過5000多行的Service類,上百個方法,代碼基本上是不可讀的。
在大多數(shù)情況下,貧血的領(lǐng)域模型沒有成本效益。它們不會給公司帶來超越其它公司的競爭優(yōu)勢,因為在這種架構(gòu)里要實現(xiàn)業(yè)務(wù)需求變更,開發(fā)并部署到生產(chǎn)環(huán)境中去要花費太長的時間。
領(lǐng)域驅(qū)動設(shè)計的分層架構(gòu)和構(gòu)成要素
下面我們簡單介紹一下領(lǐng)域驅(qū)動設(shè)計的分層架構(gòu)和構(gòu)成要素,這部分內(nèi)容在Eric Evans的書中有非常詳盡的描述,想要詳細了解的,最好去讀原版書籍。
下面這張圖是該書中著名的分層架構(gòu)圖,如下:
整個架構(gòu)分為四層,其核心就是領(lǐng)域?qū)?#xff08;Domain),所有的業(yè)務(wù)邏輯應(yīng)該在領(lǐng)域?qū)訉崿F(xiàn),具體描述如下:
| 用戶界面/展現(xiàn)層 | 負責(zé)向用戶展現(xiàn)信息以及解釋用戶命令。 |
| 應(yīng)用層 | 很薄的一層,用來協(xié)調(diào)應(yīng)用的活動。它不包含業(yè)務(wù)邏輯。它不保留業(yè)務(wù)對象的狀態(tài),但它保有應(yīng)用任務(wù)的進度狀態(tài)。 |
| 領(lǐng)域?qū)? | 本層包含關(guān)于領(lǐng)域的信息。這是業(yè)務(wù)軟件的核心所在。在這里保留業(yè)務(wù)對象的狀態(tài),對業(yè)務(wù)對象和它們狀態(tài)的持久化被委托給了基礎(chǔ)設(shè)施層。 |
| 基礎(chǔ)設(shè)施層 | 本層作為其他層的支撐庫存在。它提供了層間的通信,實現(xiàn)對業(yè)務(wù)對象的持久化,包含對用戶界面層的支撐庫等作用。 |
領(lǐng)域驅(qū)動設(shè)計除了對系統(tǒng)架構(gòu)進行了分層描述,還對對象(Object)做了明確的職責(zé)和策略劃分:
當(dāng)然,DDD中還提出了聚合和聚合根(Aggregate Root)的概念,不過我們在實踐過程發(fā)現(xiàn)聚合根有問題復(fù)雜化的傾向,用傳統(tǒng)的聚合、組合等概念去描述領(lǐng)域?qū)ο笾g的關(guān)系更容易理解,所以這里對這個概念就不做介紹了。
事務(wù)腳本和領(lǐng)域模型
Martin Fowler 2004年所著的企業(yè)應(yīng)用架構(gòu)模式(Patterns of Enterprise Application Architecture)中的第九章領(lǐng)域邏輯模式(Domain Logic Patterns)專門介紹了事務(wù)腳本(Transaction Script)和領(lǐng)域模型(Domain Model),理解這兩種模式對設(shè)計和構(gòu)建企業(yè)應(yīng)用軟件非常有幫助,所以有必要介紹一下。
事務(wù)腳本:
事務(wù)腳本的核心是過程,通過過程的調(diào)用來組織業(yè)務(wù)邏輯,每個過程處理來自表現(xiàn)層的單個請求。大部分業(yè)務(wù)應(yīng)用都可以被看成一系列事務(wù),從某種程度上來說,通過事務(wù)腳本處理業(yè)務(wù),就像執(zhí)行一條條SQL語句來實現(xiàn)數(shù)據(jù)庫信息的處理。事務(wù)腳本把業(yè)務(wù)邏輯組織成單個過程,在過程中直接調(diào)用數(shù)據(jù)庫,業(yè)務(wù)邏輯在服務(wù)(Service)層處理。
事務(wù)腳本模式可以簡單的通過UML圖表示成這樣:
由Action層處理UI層的動作請求,將Request中的數(shù)據(jù)組裝后傳遞給BusinessService,BS層做簡單的邏輯處理后,調(diào)用數(shù)據(jù)訪問對象進行數(shù)據(jù)持久化,其中VO充當(dāng)了數(shù)據(jù)傳輸對象的作用,一般是貧血的POJO,只具備getter和setter方法,沒有狀態(tài)和行為。
事務(wù)腳本模式的特點是簡單容易理解,面向過程設(shè)計。對于少量邏輯的業(yè)務(wù)應(yīng)用來說,事務(wù)腳本模式簡單自然,性能良好,容易理解,而且一個事務(wù)的處理不會影響其他事務(wù)。不過缺點也很明顯,對于復(fù)雜的業(yè)務(wù)邏輯處理力不從心,難以保持良好的設(shè)計,事務(wù)之間的冗余代碼不斷增多,通過復(fù)制粘貼方式進行復(fù)用。可維護性和擴展性變差。
領(lǐng)域模型:
領(lǐng)域模型的特點也比較明顯, 屬于面向?qū)ο笤O(shè)計,領(lǐng)域模型具備自己的屬性行為狀態(tài),并與現(xiàn)實世界的業(yè)務(wù)對象相映射。各類具備明確的職責(zé)劃分,領(lǐng)域?qū)ο笤刂g通過聚合和引用等關(guān)系配合解決實際業(yè)務(wù)應(yīng)用和規(guī)則??蓮?fù)用,可維護,易擴展,可以采用合適的設(shè)計模型進行詳細設(shè)計。缺點是相對復(fù)雜,要求設(shè)計人員有良好的抽象能力。
領(lǐng)域模型對應(yīng)的就是領(lǐng)域驅(qū)動設(shè)計中劃分的領(lǐng)域?qū)?#xff0c;這里就不詳細討論了。
在實際的設(shè)計中,我們需要根據(jù)具體的需求選擇相應(yīng)的設(shè)計模式。具備復(fù)雜業(yè)務(wù)邏輯的核心業(yè)務(wù)系統(tǒng)適合使用領(lǐng)域模型,簡單的信息管理系統(tǒng)可以考慮采用事務(wù)腳本模式。
領(lǐng)域驅(qū)動設(shè)計實踐
下面主要講一下我們在構(gòu)建企業(yè)級應(yīng)用開發(fā)平臺中對DDD的實踐和擴展。
本人近年來一直在從事企業(yè)級應(yīng)用開發(fā)平臺的相關(guān)工作,GAP平臺是我們的一個軟件產(chǎn)品,用來解決企業(yè)級軟件開發(fā)過程中復(fù)用、快速開發(fā)和過程規(guī)范等問題。設(shè)計這樣一個平臺,從底層的框架上就應(yīng)該能夠支撐復(fù)雜業(yè)務(wù)邏輯的系統(tǒng)構(gòu)建,所以我們在大的架構(gòu)設(shè)計思路上采用了領(lǐng)域驅(qū)動設(shè)計的思路,并根據(jù)實際采用的技術(shù)和要實現(xiàn)的功能對DDD的四層架構(gòu)進行了細化和實現(xiàn):
整個平臺采用了JavaEE的技術(shù)及其相關(guān)的開源框架。系統(tǒng)的核心業(yè)務(wù)邏輯由Domain層處理,其中的業(yè)務(wù)服務(wù)(BusinessService)負責(zé)處理某個相對內(nèi)聚的業(yè)務(wù)邏輯單元,同時對內(nèi)對外提供本地或遠程的服務(wù)。
下面是對各層的簡要描述:
另外,我們引入了Spring的IOC容器,系統(tǒng)的控制層、領(lǐng)域?qū)雍统志没瘜釉囟加蠭OC容器統(tǒng)一管理,實現(xiàn)完全的接口分離和解耦。同時在控制、領(lǐng)域和持久化層都可以引用日志服務(wù)。
我們對領(lǐng)域驅(qū)動要素的定義上和原有的命名和含義上稍有區(qū)別。
原來的服務(wù)(Service),我們定義為業(yè)務(wù)服務(wù)(BusinessService),面向業(yè)務(wù)服務(wù)的架構(gòu)是GAP平臺的核心設(shè)計思想,一個業(yè)務(wù)服務(wù)可以由一個或多個領(lǐng)域模型和數(shù)據(jù)訪問對象(DAO)組成,去實現(xiàn)一個完整的業(yè)務(wù)邏輯單元。業(yè)務(wù)服務(wù)主要負責(zé)事務(wù)處理和維護各個領(lǐng)域?qū)ο笾g的關(guān)系,同時為上層訪問提供本地和遠程服務(wù),服務(wù)類型包括Web Service,RMI等。
領(lǐng)域?qū)ο笥蓪嶓w(Entity)和值對象(VO)構(gòu)成,實體類具備自己的屬性和行為、狀態(tài),可以聚合VO,實體類之間可以有聚合關(guān)聯(lián)等關(guān)系,可以由數(shù)據(jù)訪問對象(DAO)進行持久化。
持久化由數(shù)據(jù)訪問對象(DAO)實現(xiàn),不處理業(yè)務(wù)邏輯,主要負責(zé)實體類的持久化。提供多種持久化方式(O/R Mapping和JDBC)。
那么如何在去實現(xiàn)領(lǐng)域驅(qū)動設(shè)計呢?我們總結(jié)了以下四個步驟:
案例網(wǎng)上書店
為了更好的理解領(lǐng)域驅(qū)動設(shè)計,我們基于以上設(shè)計方法,實現(xiàn)了一套簡單的網(wǎng)上書店系統(tǒng)。
網(wǎng)上書店系統(tǒng)是采用DDD設(shè)計思想構(gòu)建的一個應(yīng)用系統(tǒng)示例。通過網(wǎng)上書店系統(tǒng),可以快速理解領(lǐng)域驅(qū)動設(shè)計。該系統(tǒng)實現(xiàn)網(wǎng)上書店的常用功能:包括瀏覽書籍、挑選書籍、提交訂單、查看訂單、自動折扣、處理訂單、取消訂單等。未登錄用戶可以瀏覽和挑選書籍;已登錄用戶可以提交和查看自己相關(guān)的訂單;管理員可以處理訂單。
經(jīng)過業(yè)務(wù)抽象,即使是這樣一個簡單的業(yè)務(wù)場景也包含了很多領(lǐng)域?qū)ο?#xff0c;例如訂單、賬戶、書籍、購物車、購物項、折扣等,通過分析和設(shè)計,我們可以得到這樣的設(shè)計圖(為了查看方便,圖中的類隱藏了屬性信息):
BookStoreAction負責(zé)處理展現(xiàn)層的請求,并把請求轉(zhuǎn)發(fā)給業(yè)務(wù)服務(wù)IBookStoreBS,業(yè)務(wù)服務(wù)負責(zé)調(diào)度上圖中顯示的領(lǐng)域?qū)ο?#xff0c;處理該場景的所有業(yè)務(wù)。
其中領(lǐng)域?qū)ο蠛同F(xiàn)實業(yè)務(wù)的對應(yīng)關(guān)系為:
- Account賬戶
- Order訂單
- Book書籍
- Cart購物車
- Item訂單項
- Discount折扣
與事務(wù)腳本的編程模式不同,領(lǐng)域驅(qū)動設(shè)計不是把業(yè)務(wù)邏輯放在BS(BusinessService)中,而是由具備屬性、行為和狀態(tài)的領(lǐng)域?qū)ο筇幚?。例如Order類,如果是貧血的POJO,那它內(nèi)部只有與數(shù)據(jù)表字段對應(yīng)的屬性以及getter和setter方法,而在領(lǐng)域驅(qū)動設(shè)計中,則是一個相對獨立的、能夠處理自身關(guān)聯(lián)業(yè)務(wù)的領(lǐng)域?qū)ο?。在本系統(tǒng)中,我們對Order的描述如下:
訂單的實現(xiàn)類是gap.template.bookstore.model.Order,類中除了聯(lián)系方式、郵寄地址等基本屬性外,還有以下領(lǐng)域相關(guān)的行為:
通過以上的描述,我們可以看到,Order類基本上覆蓋了現(xiàn)實世界中訂單這個業(yè)務(wù)的所有行為和狀態(tài),是相對內(nèi)聚的,這樣的特性使其復(fù)用性大大增加,即使未來開發(fā)新的模塊,涉及到訂單業(yè)務(wù)的,可以直接復(fù)用Order類。同時在后期維護中,如果我想了解訂單的業(yè)務(wù),直接讀Order的代碼就可以了。
從上圖中我們還可以清晰的看到各個領(lǐng)域?qū)ο笾g的關(guān)系。Order和Cart都聚合了Item,對應(yīng)都是1...n,Item聚合了Book,對應(yīng)關(guān)系1...1。Order分別與折扣、賬戶發(fā)生關(guān)聯(lián)和調(diào)用等等,整個網(wǎng)上書店的場景就這樣描述出來了。
另外,不要忘了BS,除了起到基礎(chǔ)設(shè)施的作用外(事務(wù)管理和服務(wù)共享),它還要負責(zé)調(diào)度和維護領(lǐng)域?qū)ο笾g的關(guān)系。因為總會有些業(yè)務(wù)邏輯,既不屬于這個領(lǐng)域?qū)ο?#xff0c;也不屬于那個,那這部分業(yè)務(wù)由誰來處理呢?由BS來處理。例如在管理員處理訂單這個場景中,首先需要根據(jù)訂單信息獲取賬戶,根據(jù)賬戶信息確定折扣率,同時進行余額校驗,如果校驗通過,就會調(diào)用訂單對象的dispose方法處理訂單,這個場景會涉及到Order、Account、Discount等對象,這樣的業(yè)務(wù)邏輯,應(yīng)該由BS實現(xiàn)。
IBookStoreDao是數(shù)據(jù)訪問對象,可以被BS調(diào)用,用來持久化對象,也可以被領(lǐng)域?qū)ο笠?#xff0c;用來持久化自身。
通過以上的描述,我們可以看到,整個設(shè)計和實現(xiàn)是優(yōu)雅、清晰的。業(yè)務(wù)邏輯沒有堆積在BS中,而是分散在BS和各個領(lǐng)域?qū)ο笾?#xff0c;服務(wù)和對象都與現(xiàn)實世界的業(yè)務(wù)息息相關(guān),無論是對領(lǐng)域?qū)<?、開發(fā)人員和后期維護人員,都能這種方式中獲得自己需要的內(nèi)容。
總結(jié)
我們采用領(lǐng)域驅(qū)動設(shè)計相對比較早,就我個人的檢驗和實踐而言,DDD對構(gòu)建企業(yè)級應(yīng)用開發(fā)平臺和大型核心業(yè)務(wù)系統(tǒng)的作用是非常明顯的,無論是在產(chǎn)品的穩(wěn)定性、擴展性、可維護性、生命周期等方面都有顯著的提升。
但是,由于這樣那樣的原因(復(fù)雜度、工期、開發(fā)人員能力限制等等),很多人會不自覺的抵制采用DDD,有時候一個軟件項目重寫了兩次,第二次依然不去做良好的設(shè)計。事實上采用了DDD的設(shè)計方法,我們的設(shè)計階段已經(jīng)變得非常輕量級和敏捷了,開發(fā)人員只要能夠把領(lǐng)域模型之間的關(guān)系畫出來并描述說明,并與需求人員達成一致,那么做出來的東西基本上是靠譜的。
在技術(shù)領(lǐng)域,只有主動的嘗試和提升,效果才是最明顯的。很多人問過我,如何開始學(xué)習(xí)和實踐XXX,其實很簡單,現(xiàn)在就開始吧!
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
- 上一篇: 开灯问题 简单模拟法
- 下一篇: 算法设计方法