7.1什么是DDD
DDD是Eric Evans在2003年出版的《領(lǐng)域驅(qū)動設(shè)計:軟件核心復(fù)雜性應(yīng)對之道》(Domain-Driven Design: Tackling Complexity in the Heart of Software)一書中提出的具有劃時代意義的重要概念,是指通過統(tǒng)一語言、業(yè)務(wù)抽象、領(lǐng)域劃分和領(lǐng)域建模等一系列手段來控制軟件復(fù)雜度的方法論。
DDD的革命性在于領(lǐng)域驅(qū)動設(shè)計是面向?qū)ο蠓治龅姆椒ㄕ?,它可以利用面向?qū)ο蟮奶匦裕ǚ庋b、多態(tài))有效地化解復(fù)雜性,而傳統(tǒng)J2EE或Spring+Hibernate等事務(wù)性編程模型只關(guān)心數(shù)據(jù)。這些數(shù)據(jù)對象除了簡單的setter/getter方法外,不包含任何業(yè)務(wù)邏輯,業(yè)務(wù)邏輯都是以過程式的代碼寫在Service中。這種方式極易上手,但隨著業(yè)務(wù)的發(fā)展,系統(tǒng)也很容易變得混亂復(fù)雜。
7.2初步體驗DDD
在介紹DDD之前,我喜歡用這個銀行轉(zhuǎn)賬的案例來做一個DDD和事務(wù)腳本(Transaction Script)的簡單對比。我們要實現(xiàn)一個銀行轉(zhuǎn)賬的功能,如果用傳統(tǒng)的事務(wù)腳本方式實現(xiàn),業(yè)務(wù)邏輯通常會被寫在MoneyTransferService中,而Account僅僅是getters和setters的數(shù)據(jù)結(jié)構(gòu),也就是所謂的“貧血模式”。其代碼如下所示:
publicclassMoneyTransferServiceTransactionScriptImpl implementsMoneyTransferService{ privateAccountDaoaccountDao; privateBankingTransactionRepositorybankingTransactionRepository; ... @Override publicBankingTransactiontransfer( StringfromAccountId,StringtoAccountId,doubleamount){ AccountfromAccount=accountDao.findById(fromAccountId); AccounttoAccount=accountDao.findById(toAccountId); ... doublenewBalance=fromAccount.getBalance()-amount; switch(fromAccount.getOverdraftPolicy()){ caseNEVER: if(newBalance0)?{ ????????throw?new?DebitException("Insufficient?funds"); ??????} ??????break; ????case?ALLOWED: ??????if?(newBalance?-limit)?{ ????????throw?new?DebitException( ????????????"Overdraft?limit?(of?"?+?limit?+")?exceeded:?"?+?newBalance); ??????} ??????break; ????} ????fromAccount.setBalance(newBalance); ????toAccount.setBalance(toAccount.getBalance()?+?amount); ????BankingTransaction?moneyTransferTransaction?= ????????new?MoneyTranferTransaction(fromAccountId,toAccountId,amount); ????bankingTransactionRepository.addTransaction(moneyTransferTransaction); ????return?moneyTransferTransaction; ??}}
上述代碼有些讀者可能會比較眼熟,因為大部分系統(tǒng)都是這么寫的。評審?fù)晷枨螅?a target="_blank">工程師畫幾張UML圖完成設(shè)計,就開始像上面這樣寫業(yè)務(wù)代碼了,這樣寫基本不用太動腦筋,完全是過程式的代碼風(fēng)格。
同樣的業(yè)務(wù)邏輯,接下來看使用領(lǐng)域建模是怎么做的。在使用DDD之后,Account實體除賬號屬性之外,還包含了行為和業(yè)務(wù)邏輯,比如debit()和credit()方法。
publicclassAccount{ privateStringid; privatedoublebalance; privateOverdraftPolicyoverdraftPolicy; ... publicdoublebalance(){returnbalance;} publicvoiddebit(doubleamount){ this.overdraftPolicy.preDebit(this,amount); this.balance=this.balance-amount; this.overdraftPolicy.postDebit(this,amount); } publicvoidcredit(doubleamount){ this.balance=this.balance+amount; }}
透支策略O(shè)verdraftPolicy也不僅僅是一個Enum了,而是被抽象成包含業(yè)務(wù)規(guī)則并采用策略模式的對象。
publicinterfaceOverdraftPolicy{ voidpreDebit(Accountaccount,doubleamount); voidpostDebit(Accountaccount,doubleamount);}publicclassNoOverdraftAllowedimplementsOverdraftPolicy{ publicvoidpreDebit(Accountaccount,doubleamount){ doublenewBalance=account.balance()-amount; if(newBalance0)?{ ??????throw?new?DebitException("Insufficient?funds"); ????} ??} ??public?void?postDebit(Account?account,?double?amount)?{ ??}}public?class?LimitedOverdraft?implements?OverdraftPolicy?{ ??private?double?limit; ??.?.?. ??public?void?preDebit(Account?account,?double?amount)?{ ????double?newBalance?=?account.balance()?-?amount; ????if?(newBalance?-limit)?{ ??????throw?new?DebitException( ??????????"Overdraft?limit?(of?"?+?limit?+?")?exceeded:?"+newBalance); ????} ??} ??public?void?postDebit(Account?account,?double?amount)?{ ??}}
而Domain Service只需要調(diào)用Domain Entity對象完成業(yè)務(wù)邏輯。
publicclassMoneyTransferServiceDomainModelImpl implementsMoneyTransferService{ privateAccountRepositoryaccountRepository; privateBankingTransactionRepositorybankingTransactionRepository; ... @Override publicBankingTransactiontransfer( StringfromAccountId,StringtoAccountId,doubleamount){ AccountfromAccount=accountRepository.findById(fromAccountId); AccounttoAccount=accountRepository.findById(toAccountId); ... fromAccount.debit(amount); toAccount.credit(amount); BankingTransactionmoneyTransferTransaction= newMoneyTranferTransaction(fromAccountId,toAccountId,amount); bankingTransactionRepository.addTransaction(moneyTransferTransaction); returnmoneyTransferTransaction; }}
通過DDD重構(gòu)后,雖然類的數(shù)量比以前多了一些,但是每個類的職責(zé)更加單一,代碼的可讀性和可擴(kuò)展性也隨之提高。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
7.3數(shù)據(jù)驅(qū)動和領(lǐng)域驅(qū)動
7.3.1數(shù)據(jù)驅(qū)動
目前主流的開發(fā)模式是由數(shù)據(jù)驅(qū)動的。數(shù)據(jù)驅(qū)動的開發(fā)很容易上手,
有了業(yè)務(wù)需求,創(chuàng)建數(shù)據(jù)庫表,然后編寫業(yè)務(wù)邏輯,開發(fā)過程如圖7-1所示。數(shù)據(jù)驅(qū)動以數(shù)據(jù)庫為中心,其中最重要的設(shè)計是數(shù)據(jù)模型,但隨著業(yè)務(wù)的增長和項目的推進(jìn),軟件開發(fā)和維護(hù)的難度會急劇增加。
圖7-1數(shù)據(jù)驅(qū)動研發(fā)過程
以客戶關(guān)系管理(Customer Relationship Management,CRM)為例,其中很重要的概念有銷售、機會、客戶、私海、公海,實體的定義分別如下。
銷售(Sales):公司的銷售人員,一個銷售可以擁有多個銷售機會。
機會(Opportunity):銷售機會,每個機會包含至少一個客戶信息,且歸屬于一個銷售人員。
客戶(Customer):客戶,也就是銷售的對象。
私海(Private sea):專屬于某個銷售人員的領(lǐng)地(Territory),私海里面的客戶,其他銷售人員不能觸碰。
公海(Public sea):公共的領(lǐng)地,所有銷售人員都可以從公海里撿入客戶到其私海。
按照我們曾經(jīng)學(xué)習(xí)的數(shù)據(jù)庫建模理論,對于上面的場景,不難畫出圖7-2所示的實體聯(lián)系(Entity Relationship,ER)圖。
圖7-2CRM的ER圖
可以看到,圖7-2所示的ER圖中不存在公海和私海,因為所謂的機會在私海,就是這個機會是不是歸屬某個銷售,這樣我們只需要看機會上是否有salesId。如果有,說明機會被某個銷售占有,也就是在私海中;反之,這個機會就在公海中。
在這種開發(fā)模式下,最后的產(chǎn)出是幾張數(shù)據(jù)庫表,以及針對表中數(shù)據(jù)進(jìn)行操作的事務(wù)腳本,如圖7-3所示。
圖7-3事務(wù)腳本實現(xiàn)
7.3.2領(lǐng)域驅(qū)動
領(lǐng)域驅(qū)動設(shè)計關(guān)心的是業(yè)務(wù)中的領(lǐng)域劃分(戰(zhàn)略設(shè)計)和領(lǐng)域建模(戰(zhàn)術(shù)設(shè)計),其開發(fā)過程不再以數(shù)據(jù)模型為起點,而是以領(lǐng)域模型為出發(fā)點,研發(fā)過程如圖7-4所示。領(lǐng)域模型對應(yīng)的是業(yè)務(wù)實體,在程序中主要表現(xiàn)為類、聚合根和值對象,它更加關(guān)注業(yè)務(wù)語義的顯性化表達(dá),而不是數(shù)據(jù)的存儲和數(shù)據(jù)之間的關(guān)系。 這是“領(lǐng)域驅(qū)動設(shè)計”和“數(shù)據(jù)驅(qū)動設(shè)計”之間顯著的區(qū)別。
圖7-4領(lǐng)域驅(qū)動研發(fā)過程
仍以上面的CRM為例。假如我們先不考慮數(shù)據(jù)模型,而是采用面向?qū)ο蠓治觯∣bject Oriented Analysis,OOA)對這個場景進(jìn)行領(lǐng)域建模,那么可以得到圖7-5所示的領(lǐng)域模型。
圖7-5CRM的領(lǐng)域模型
可以看到,在圖7-5中,領(lǐng)域模型的描述更加貼近業(yè)務(wù),一些重要的業(yè)務(wù)術(shù)語和概念沒有丟失,更完整地表達(dá)了業(yè)務(wù)語義。即使是產(chǎn)品經(jīng)理或者業(yè)務(wù)人員,也不難看懂這樣的領(lǐng)域模型,甚至他們可以和技術(shù)人員一起參與到梳理領(lǐng)域模型和創(chuàng)建活動中來。
通過DDD的戰(zhàn)略設(shè)計和戰(zhàn)術(shù)設(shè)計,我們可以為問題域劃分出合適的子域,并對域中的業(yè)務(wù)進(jìn)行建模。圖7-6所示是我們在實際工作中為CRM進(jìn)行的領(lǐng)域戰(zhàn)略設(shè)計。
圖7-6CRM的領(lǐng)域劃分
7.3.3ORM
很明顯,領(lǐng)域模型和數(shù)據(jù)模型并不是一一對應(yīng)的關(guān)系,但也不排除,有些情況領(lǐng)域模型和數(shù)據(jù)模型是趨同的,但是大部分情況都需要做一層映射(Mapping)。為了彌補二者之間的差異,行業(yè)先驅(qū)們做了很多關(guān)于映射工作的嘗試,這種技術(shù)有一個名稱叫作對象關(guān)系映射(Object Relationship Mapping,ORM),如圖7-7所示。
圖7-7對象關(guān)系映射
ORM曾經(jīng)非?;穑浀卯?dāng)年Hibernate才出現(xiàn)時,我用盡了其中的高級技巧,比如繼承關(guān)系映射、多對多關(guān)系映射……結(jié)果弄出來的東西卻變成了“四不像”,既不像Entity,也不像數(shù)據(jù)對象(Data Object,DO)。
ORM的問題在于它太理想化,期望通過工具把數(shù)據(jù)建模和領(lǐng)域建模合一,這樣的嘗試注定是很難成功的。仍以上述的CRM案例為例,在數(shù)據(jù)模型中根本就沒有私海和公海這兩個實體,工具是無法映射的。因此,Hibernate和JPA的衰落是可以預(yù)見的?,F(xiàn)在使用最多的是MyBatis,它很簡單,完全不理會復(fù)雜的關(guān)系和對象之間的復(fù)雜關(guān)系映射,只做數(shù)據(jù)庫表和DO之間的簡單映射。
復(fù)雜的數(shù)據(jù)庫關(guān)系和對象關(guān)系之間的差異,其本質(zhì)是數(shù)據(jù)模型和領(lǐng)域模型之間的差異,而這種差異的多樣性和靈活性是很難通過規(guī)則預(yù)先定義的,這也是為什么工具的作用會很有限?,F(xiàn)在的互聯(lián)網(wǎng)大廠大多使用MyBatis,原因也在于此。因此,如果你打算實踐DDD,請一定不要讓工具幫你去建模,工具不會抽象,也不會思考,還是要老老實實自己動手去建。
審核編輯:劉清
-
CRM
+關(guān)注
關(guān)注
1文章
145瀏覽量
21160 -
數(shù)據(jù)驅(qū)動器
+關(guān)注
關(guān)注
0文章
5瀏覽量
6200 -
ddd
+關(guān)注
關(guān)注
0文章
23瀏覽量
2960
原文標(biāo)題:DDD的精髓
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
如何進(jìn)行FSM任意狀態(tài)建模 ?
基于領(lǐng)域建模的數(shù)控系統(tǒng)代碼生成技術(shù)
綜合航電領(lǐng)域元建模技術(shù)研究
詳解領(lǐng)域驅(qū)動設(shè)計和spring
![詳解<b class='flag-5'>領(lǐng)域</b><b class='flag-5'>驅(qū)動</b>設(shè)計和spring](https://file.elecfans.com/web2/M00/49/E0/pYYBAGKhvG6ABCSSAAAwbONWiLw601.png)
詳解領(lǐng)域驅(qū)動設(shè)計和spring
![詳解<b class='flag-5'>領(lǐng)域</b><b class='flag-5'>驅(qū)動</b>設(shè)計和spring](https://file.elecfans.com/web2/M00/49/E0/pYYBAGKhvG6APs_TAAAwbONWiLw370.png)
如何進(jìn)行正確數(shù)據(jù)建模
如何進(jìn)行OPCDCOM配置
![如<b class='flag-5'>何進(jìn)行</b>OPCDCOM配置](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
SystemVerilog對硬件功能如何進(jìn)行建模
用好DDD必須先過Spring Data這關(guān)
一文理解DDD領(lǐng)域驅(qū)動設(shè)計
![一文理解<b class='flag-5'>DDD</b><b class='flag-5'>領(lǐng)域</b><b class='flag-5'>驅(qū)動</b>設(shè)計](https://file1.elecfans.com/web2/M00/88/AC/wKgaomRu_sSAGwklAAATpzreM7w877.jpg)
cad如何進(jìn)行三維建模
DDD學(xué)習(xí)與感悟——向屎山?jīng)_鋒
![<b class='flag-5'>DDD</b>學(xué)習(xí)與感悟——向屎山?jīng)_鋒](https://file1.elecfans.com//web2/M00/07/AF/wKgZombyTo2AZoglAAGro_B2Gyo735.png)
評論