0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一文理解DDD領(lǐng)域驅(qū)動設(shè)計

Android編程精選 ? 來源:SpringForAll社區(qū) ? 2023-05-25 14:21 ? 次閱讀

2004年Eric Evans 發(fā)表Domain-Driven Design –Tackling Complexity in the Heart of Software (領(lǐng)域驅(qū)動設(shè)計),簡稱Evans DDD。領(lǐng)域驅(qū)動設(shè)計分為兩個階段:

以一種領(lǐng)域?qū)<?、設(shè)計人員、開發(fā)人員都能理解的通用語言作為相互交流的工具,在交流的過程中發(fā)現(xiàn)領(lǐng)域概念,然后將這些概念設(shè)計成一個領(lǐng)域模型;由領(lǐng)域模型驅(qū)動軟件設(shè)計,用代碼來實現(xiàn)該領(lǐng)域模型;

由此可見,領(lǐng)域驅(qū)動設(shè)計的核心是建立正確的領(lǐng)域模型。

領(lǐng)域驅(qū)動設(shè)計告訴我們,在通過軟件實現(xiàn)一個業(yè)務(wù)系統(tǒng)時,建立一個領(lǐng)域模型是非常重要和必要的,因為領(lǐng)域模型具有以下特點:

領(lǐng)域模型是對具有某個邊界的領(lǐng)域的一個抽象,反映了領(lǐng)域內(nèi)用戶業(yè)務(wù)需求的本質(zhì);領(lǐng)域模型是有邊界的,只反應(yīng)了我們在領(lǐng)域內(nèi)所關(guān)注的部分;

領(lǐng)域模型只反映業(yè)務(wù),和任何技術(shù)實現(xiàn)無關(guān);領(lǐng)域模型不僅能反映領(lǐng)域中的一些實體概念,如貨物,書本,應(yīng)聘記錄,地址,等;還能反映領(lǐng)域中的一些過程概念,如資金轉(zhuǎn)賬,等;

領(lǐng)域模型確保了我們的軟件的業(yè)務(wù)邏輯都在一個模型中,都在一個地方;這樣對提高軟件的可維護(hù)性,業(yè)務(wù)可理解性以及可重用性方面都有很好的幫助;

領(lǐng)域模型能夠幫助開發(fā)人員相對平滑地將領(lǐng)域知識轉(zhuǎn)化為軟件構(gòu)造;

領(lǐng)域模型貫穿軟件分析、設(shè)計,以及開發(fā)的整個過程;領(lǐng)域?qū)<摇⒃O(shè)計人員、開發(fā)人員通過領(lǐng)域模型進(jìn)行交流,彼此共享知識與信息;因為大家面向的都是同一個模型,所以可以防止需求走樣,可以讓軟件設(shè)計開發(fā)人員做出來的軟件真正滿足需求;

要建立正確的領(lǐng)域模型并不簡單,需要領(lǐng)域?qū)<?、設(shè)計、開發(fā)人員積極溝通共同努力,然后才能使大家對領(lǐng)域的認(rèn)識不斷深入,從而不斷細(xì)化和完善領(lǐng)域模型;

為了讓領(lǐng)域模型看的見,我們需要用一些方法來表示它;圖是表達(dá)領(lǐng)域模型最常用的方式,但不是唯一的表達(dá)方式,代碼或文字描述也能表達(dá)領(lǐng)域模型;

領(lǐng)域模型是整個軟件的核心,是軟件中最有價值和最具競爭力的部分;設(shè)計足夠精良且符合業(yè)務(wù)需求的領(lǐng)域模型能夠更快速的響應(yīng)需求變化;

我們認(rèn)識到由軟件專家和領(lǐng)域?qū)<彝献鏖_發(fā)出一個領(lǐng)域的模型是絕對需要的,但是,那種方法通常會由于一些基礎(chǔ)交流的障礙而存在難點。開發(fā)人員滿腦子都是類、方法、算法、模式、架構(gòu),等等,總是想將實際生活中的概念和程序工件進(jìn)行對應(yīng)。他們希望看到要建立哪些對象類,要如何對對象類之間的關(guān)系建模。他們會習(xí)慣按照封裝、繼承、多態(tài)等面向?qū)ο?a href="http://www.ttokpm.com/v/tag/1315/" target="_blank">編程中的概念去思考,會隨時隨地這樣交談,這對他們來說這太正常不過了,開發(fā)人員就是開發(fā)人員。但是領(lǐng)域?qū)<彝ǔ@一無所知,他們對軟件類庫、框架、持久化甚至數(shù)據(jù)庫沒有什么概念。他們只了解他們特有的領(lǐng)域?qū)I(yè)技能。比如,在空中交通監(jiān)控樣例中,領(lǐng)域?qū)<抑里w機(jī)、路線、海拔、經(jīng)度、緯度,知道飛機(jī)偏離了正常路線,知道飛機(jī)的發(fā)射。他們用他們自己的術(shù)語討論這些事情,有時這對于外行來說很難直接理解。如果一個人說了什么事情,其他的人不能理解,或者更糟的是錯誤理解成其他事情,又有什么機(jī)會來保證項目成功呢?

在交流的過程中,需要做翻譯才能讓其他的人理解這些概念。開發(fā)人員可能會努力使用外行人的語言來解析一些設(shè)計模式,但這并一定都能成功奏效。領(lǐng)域?qū)<乙部赡軙?chuàng)建一種新的行話以努力表達(dá)他們的這些想法。在這個痛苦的交流過程中,這種類型的翻譯并不能對知識的構(gòu)建過程產(chǎn)生幫助。

領(lǐng)域驅(qū)動設(shè)計的一個核心的原則是使用一種基于模型的語言。因為模型是軟件滿足領(lǐng)域的共同點,它很適合作為這種通用語言的構(gòu)造基礎(chǔ)。使用模型作為語言的核心骨架,要求團(tuán)隊在進(jìn)行所有的交流是都使用一致的語言,在代碼中也是這樣。在共享知識和推敲模型時,團(tuán)隊會使用演講、文字和圖形。這兒需要確保團(tuán)隊使用的語言在所有的交流形式中看上去都是一致的,這種語言被稱為“通用語言(Ubiquitous Language)”。通用語言應(yīng)該在建模過程中廣泛嘗試以推動軟件專家和領(lǐng)域?qū)<抑g的溝通,從而發(fā)現(xiàn)要在模型中使用的主要的領(lǐng)域概念。

擁有一個看上去正確的模型不代表模型能被直接轉(zhuǎn)換成代碼,也或者它的實現(xiàn)可能會違背某些我們所不建議的軟件設(shè)計原則。我們該如何實現(xiàn)從模型到代碼的轉(zhuǎn)換,并讓代碼具有可擴(kuò)展性、可維護(hù)性,高性能等指標(biāo)呢?另外,如實反映領(lǐng)域的模型可能會導(dǎo)致對象持久化的一系列問題,或者導(dǎo)致不可接受的性能問題。那么我們應(yīng)該怎么做呢?

我們應(yīng)該緊密關(guān)聯(lián)領(lǐng)域建模和設(shè)計,緊密將領(lǐng)域模型和軟件編碼實現(xiàn)捆綁在一起,模型在構(gòu)建時就考慮到軟件和設(shè)計。開發(fā)人員會被加入到建模的過程中來。主要的想法是選擇一個能夠恰當(dāng)在軟件中表現(xiàn)的模型,這樣設(shè)計過程會很順暢并且基于模型。代碼和其下的模型緊密關(guān)聯(lián)會讓代碼更有意義并與模型更相關(guān)。有了開發(fā)人員的參與就會有反饋。它能保證模型被實現(xiàn)成軟件。如果其中某處有錯誤,會在早期就被標(biāo)識出來,問題也會容易修正。寫代碼的人會很好地了解模型,會感覺自己有責(zé)任保持它的完整性。他們會意識到對代碼的一個變更其實就隱含著對模型的變更,另外,如果哪里的代碼不能表現(xiàn)原始模型的話,他們會重構(gòu)代碼。如果分析人員從實現(xiàn)過程中分離出去,他會不再關(guān)心開發(fā)過程中引入的局限性。最終結(jié)果是模型不再實用。任何技術(shù)人員想對模型做出貢獻(xiàn)必須花費一些時間來接觸代碼,無論他在項目中擔(dān)負(fù)的是什么主要角色。任何一個負(fù)責(zé)修改代碼的人都必須學(xué)會用代碼表現(xiàn)模型。每位開發(fā)人員都必須參與到一定級別的領(lǐng)域討論中并和領(lǐng)域?qū)<衣?lián)絡(luò)。

“用戶需求”不能等同于“用戶”,捕捉“用戶心中的模型”也不能等同于“以用戶為核心設(shè)計領(lǐng)域模型”?!独献印窌杏袀€觀點:有之以為利,無之以為用。在這里,有之利,即建立領(lǐng)域模型;無之用,即包容用戶需求。舉些例子,一個杯子要裝滿一杯水,我們在制作杯子時,制作的是空杯子,即要把水倒出來,之后才能裝下水;再比如,一座房子要住人,我們在建造房子時,建造的房子是空的,唯有空的才能容納人的居住。因此,建立領(lǐng)域模型時也要將用戶置于模型之外,這樣才能包容用戶的需求。

所以,我的理解是:

我們設(shè)計領(lǐng)域模型時不能以用戶為中心作為出發(fā)點去思考問題,不能老是想著用戶會對系統(tǒng)做什么;而應(yīng)該從一個客觀的角度,根據(jù)用戶需求挖掘出領(lǐng)域內(nèi)的相關(guān)事物,思考這些事物的本質(zhì)關(guān)聯(lián)及其變化規(guī)律作為出發(fā)點去思考問題。

領(lǐng)域模型是排除了人之外的客觀世界模型,但是領(lǐng)域模型包含人所扮演的參與者角色,但是一般情況下不要讓參與者角色在領(lǐng)域模型中占據(jù)主要位置,如果以人所扮演的參與者角色在領(lǐng)域模型中占據(jù)主要位置,那么各個系統(tǒng)的領(lǐng)域模型將變得沒有差別,因為軟件系統(tǒng)就是一個人機(jī)交互的系統(tǒng),都是以人為主的活動記錄或跟蹤;比如:論壇中如果以人為主導(dǎo),那么領(lǐng)域模型就是:人發(fā)帖,人回帖,人結(jié)貼,等等;DDD的例子中,如果是以人為中心的話,就變成了:托運人托運貨物,收貨人收貨物,付款人付款,等等;因此,當(dāng)我們談及領(lǐng)域模型時,已經(jīng)默認(rèn)把人的因素排除開了,因為領(lǐng)域只有對人來說才有意義,人是在領(lǐng)域范圍之外的,如果人也劃入領(lǐng)域,領(lǐng)域模型將很難保持客觀性。領(lǐng)域模型是與誰用和怎樣用是無關(guān)的客觀模型。歸納起來說就是,領(lǐng)域建模是建立虛擬模型讓我們現(xiàn)實的人使用,而不是建立虛擬空間,去模仿現(xiàn)實。

以Eric Evans(DDD之父)在他的書中的一個貨物運輸系統(tǒng)為例子簡單說明一下。在經(jīng)過一些用戶需求討論之后,在用戶需求相對明朗之后,Eric這樣描述領(lǐng)域模型:

一個Cargo(貨物)涉及多個Customer(客戶,如托運人、收貨人、付款人),每個Customer承擔(dān)不同的角色;

Cargo的運送目標(biāo)已指定,即Cargo有一個運送目標(biāo);

由一系列滿足Specification(規(guī)格)的Carrier Movement(運輸動作)來完成運輸目標(biāo);

從上面的描述我們可以看出,他完全沒有從用戶的角度去描述領(lǐng)域模型,而是以領(lǐng)域內(nèi)的相關(guān)事物為出發(fā)點,考慮這些事物的本質(zhì)關(guān)聯(lián)及其變化規(guī)律的。上述這段描述完全以貨物為中心,把客戶看成是貨物在某個場景中可能會涉及到的關(guān)聯(lián)角色,如貨物會涉及到托運人、收貨人、付款人;貨物有一個確定的目標(biāo),貨物會經(jīng)過一系列列的運輸動作到達(dá)目的地;其實,我覺得以用戶為中心來思考領(lǐng)域模型的思維只是停留在需求的表面,而沒有挖掘出真正的需求的本質(zhì);我們在做領(lǐng)域建模時需要努力挖掘用戶需求的本質(zhì),這樣才能真正實現(xiàn)用戶需求;

關(guān)于用戶、參與者這兩個概念的區(qū)分,可以看一下下面的例子:

試想兩個人共同玩足球游戲,操作者(用戶)是驅(qū)動者,它驅(qū)使足球比賽領(lǐng)域中,各個“人”(參與者)的活動。這里立下一個假設(shè),假設(shè)操作者A操作某一隊員a,而隊員a擁有著某人B的信息,那么有以下說法,a是B的鏡像,a是領(lǐng)域參與者,A是驅(qū)動者。

7291e052-fab1-11ed-90ce-dac502259ad0.jpg

用戶界面/展現(xiàn)層

負(fù)責(zé)向用戶展現(xiàn)信息以及解釋用戶命令。更細(xì)的方面來講就是:

請求應(yīng)用層以獲取用戶所需要展現(xiàn)的數(shù)據(jù);

發(fā)送命令給應(yīng)用層要求其執(zhí)行某個用戶命令;

應(yīng)用層

很薄的一層,定義軟件要完成的所有任務(wù)。對外為展現(xiàn)層提供各種應(yīng)用功能(包括查詢或命令),對內(nèi)調(diào)用領(lǐng)域?qū)樱I(lǐng)域?qū)ο蠡蝾I(lǐng)域服務(wù))完成各種業(yè)務(wù)邏輯,應(yīng)用層不包含業(yè)務(wù)邏輯。

領(lǐng)域?qū)?/p>

負(fù)責(zé)表達(dá)業(yè)務(wù)概念,業(yè)務(wù)狀態(tài)信息以及業(yè)務(wù)規(guī)則,領(lǐng)域模型處于這一層,是業(yè)務(wù)軟件的核心。

基礎(chǔ)設(shè)施層

本層為其他層提供通用的技術(shù)能力;提供了層間的通信;為領(lǐng)域?qū)訉崿F(xiàn)持久化機(jī)制;總之,基礎(chǔ)設(shè)施層可以通過架構(gòu)和框架來支持其他層的技術(shù)需求;

所有模式的總攬圖

72bae6be-fab1-11ed-90ce-dac502259ad0.jpg

關(guān)聯(lián)的設(shè)計

關(guān)聯(lián)本身不是一個模式,但它在領(lǐng)域建模的過程中非常重要,所以需要在探討各種模式之前,先討論一下對象之間的關(guān)聯(lián)該如何設(shè)計。我覺得對象的關(guān)聯(lián)的設(shè)計可以遵循如下的一些原則:

關(guān)聯(lián)盡量少,對象之間的復(fù)雜的關(guān)聯(lián)容易形成對象的關(guān)系網(wǎng),這樣對于我們理解和維護(hù)單個對象很不利,同時也很難劃分對象與對象之間的邊界;另外,同時減少關(guān)聯(lián)有助于簡化對象之間的遍歷;

對多的關(guān)聯(lián)也許在業(yè)務(wù)上是很自然的,通常我們會用一個集合來表示1對多的關(guān)系。但我們往往也需要考慮到性能問題,尤其是當(dāng)集合內(nèi)元素非常多的時候,此時往往需要通過單獨查詢來獲取關(guān)聯(lián)的集合信息;

關(guān)聯(lián)盡量保持單向的關(guān)聯(lián);

在建立關(guān)聯(lián)時,我們需要深入去挖掘是否存在關(guān)聯(lián)的限制條件,如果存在,那么最好把這個限制條件加到這個關(guān)聯(lián)上;往往這樣的限制條件能將關(guān)聯(lián)化繁為簡,即可以將多對多簡化為1對多,或?qū)?對多簡化為1對1;

實體(Entity)

實體就是領(lǐng)域中需要唯一標(biāo)識的領(lǐng)域概念。因為我們有時需要區(qū)分是哪個實體。有兩個實體,如果唯一標(biāo)識不一樣,那么即便實體的其他所有屬性都一樣,我們也認(rèn)為他們兩個不同的實體;因為實體有生命周期,實體從被創(chuàng)建后可能會被持久化到數(shù)據(jù)庫,然后某個時候又會被取出來。所以,如果我們不為實體定義一種可以唯一區(qū)分的標(biāo)識,那我們就無法區(qū)分到底是這個實體還是哪個實體。另外,不應(yīng)該給實體定義太多的屬性或行為,而應(yīng)該尋找關(guān)聯(lián),發(fā)現(xiàn)其他一些實體或值對象,將屬性或行為轉(zhuǎn)移到其他關(guān)聯(lián)的實體或值對象上。比如Customer實體,他有一些地址信息,由于地址信息是一個完整的有業(yè)務(wù)含義的概念,所以,我們可以定義一個Address對象,然后把Customer的地址相關(guān)的信息轉(zhuǎn)移到Address對象上。如果沒有Address對象,而把這些地址信息直接放在Customer對象上,并且如果對于一些其他的類似Address的信息也都直接放在Customer上,會導(dǎo)致Customer對象很混亂,結(jié)構(gòu)不清晰,最終導(dǎo)致它難以維護(hù)和理解;

值對象(Value Object)

在領(lǐng)域中,并不是沒一個事物都必須有一個唯一標(biāo)識,也就是說我們不關(guān)心對象是哪個,而只關(guān)心對象是什么。就以上面的地址對象Address為例,如果有兩個Customer的地址信息是一樣的,我們就會認(rèn)為這兩個Customer的地址是同一個。也就是說只要地址信息一樣,我們就認(rèn)為是同一個地址。用程序的方式來表達(dá)就是,如果兩個對象的所有的屬性的值都相同我們會認(rèn)為它們是同一個對象的話,那么我們就可以把這種對象設(shè)計為值對象。因此,值對象沒有唯一標(biāo)識,這是它和實體的最大不同。另外值對象在判斷是否是同一個對象時是通過它們的所有屬性是否相同,如果相同則認(rèn)為是同一個值對象;而我們在區(qū)分是否是同一個實體時,只看實體的唯一標(biāo)識是否相同,而不管實體的屬性是否相同;值對象另外一個明顯的特征是不可變,即所有屬性都是只讀的。因為屬性是只讀的,所以可以被安全的共享;當(dāng)共享值對象時,一般有復(fù)制和共享兩種做法,具體采用哪種做法還要根據(jù)實際情況而定;另外,我們應(yīng)該給值對象設(shè)計的盡量簡單,不要讓它引用很多其他的對象,因為他只是一個值,就像int a = 3;那么”3”就是一個我們傳統(tǒng)意義上所說的值,而值對象其實也可以和這里的”3”一樣理解,也是一個值,只不過是用對象來表示。所以,當(dāng)我們在C#語言中比較兩個值對象是否相等時,會重寫GetHashCode和Equals這兩個方法,目的就是為了比較對象的值;值對象雖然是只讀的,但是可以被整個替換掉。就像你把a的值修改為”4”(a = 4;)一樣,直接把”3”這個值替換為”4”了。值對象也是一樣,當(dāng)你要修改Customer的Address對象引用時,不是通過Customer.Address.Street這樣的方式來實現(xiàn),因為值對象是只讀的,它是一個完整的不可分割的整體。我們可以這樣做:Customer.Address = new Address(…);

領(lǐng)域服務(wù)(Domain Service)

領(lǐng)域中的一些概念不太適合建模為對象,即歸類到實體對象或值對象,因為它們本質(zhì)上就是一些操作,一些動作,而不是事物。這些操作或動作往往會涉及到多個領(lǐng)域?qū)ο?,并且需要協(xié)調(diào)這些領(lǐng)域?qū)ο蠊餐瓿蛇@個操作或動作。如果強(qiáng)行將這些操作職責(zé)分配給任何一個對象,則被分配的對象就是承擔(dān)一些不該承擔(dān)的職責(zé),從而會導(dǎo)致對象的職責(zé)不明確很混亂。但是基于類的面向?qū)ο笳Z言規(guī)定任何屬性或行為都必須放在對象里面。所以我們需要尋找一種新的模式來表示這種跨多個對象的操作,DDD認(rèn)為服務(wù)是一個很自然的范式用來對應(yīng)這種跨多個對象的操作,所以就有了領(lǐng)域服務(wù)這個模式。和領(lǐng)域?qū)ο蟛煌I(lǐng)域服務(wù)是以動詞開頭來命名的,比如資金轉(zhuǎn)帳服務(wù)可以命名為MoneyTransferService。當(dāng)然,你也可以把服務(wù)理解為一個對象,但這和一般意義上的對象有些區(qū)別。因為一般的領(lǐng)域?qū)ο蠖际怯袪顟B(tài)和行為的,而領(lǐng)域服務(wù)沒有狀態(tài)只有行為。需要強(qiáng)調(diào)的是領(lǐng)域服務(wù)是無狀態(tài)的,它存在的意義就是協(xié)調(diào)領(lǐng)域?qū)ο蠊餐瓿赡硞€操作,所有的狀態(tài)還是都保存在相應(yīng)的領(lǐng)域?qū)ο笾小N矣X得模型(實體)與服務(wù)(場景)是對領(lǐng)域的一種劃分,模型關(guān)注領(lǐng)域的個體行為,場景關(guān)注領(lǐng)域的群體行為,模型關(guān)注領(lǐng)域的靜態(tài)結(jié)構(gòu),場景關(guān)注領(lǐng)域的動態(tài)功能。這也符合了現(xiàn)實中出現(xiàn)的各種現(xiàn)象,有動有靜,有獨立有協(xié)作。

領(lǐng)域服務(wù)還有一個很重要的功能就是可以避免領(lǐng)域邏輯泄露到應(yīng)用層。因為如果沒有領(lǐng)域服務(wù),那么應(yīng)用層會直接調(diào)用領(lǐng)域?qū)ο笸瓿杀驹撌菍儆陬I(lǐng)域服務(wù)該做的操作,這樣一來,領(lǐng)域?qū)涌赡軙岩徊糠诸I(lǐng)域知識泄露到應(yīng)用層。因為應(yīng)用層需要了解每個領(lǐng)域?qū)ο蟮臉I(yè)務(wù)功能,具有哪些信息,以及它可能會與哪些其他領(lǐng)域?qū)ο蠼换?,怎么交互等一系列領(lǐng)域知識。因此,引入領(lǐng)域服務(wù)可以有效的防治領(lǐng)域?qū)拥倪壿嬓孤兜綉?yīng)用層。對于應(yīng)用層來說,從可理解的角度來講,通過調(diào)用領(lǐng)域服務(wù)提供的簡單易懂但意義明確的接口肯定也要比直接操縱領(lǐng)域?qū)ο笕菀椎亩?。這里似乎也看到了領(lǐng)域服務(wù)具有Fa?ade的功能,呵呵。

說到領(lǐng)域服務(wù),還需要提一下軟件中一般有三種服務(wù):應(yīng)用層服務(wù)、領(lǐng)域服務(wù)、基礎(chǔ)服務(wù)。

應(yīng)用層服務(wù)

獲取輸入(如一個XML請求);

發(fā)送消息給領(lǐng)域?qū)臃?wù),要求其實現(xiàn)轉(zhuǎn)帳的業(yè)務(wù)邏輯;

領(lǐng)域?qū)臃?wù)處理成功,則調(diào)用基礎(chǔ)層服務(wù)發(fā)送Email通知;

領(lǐng)域?qū)臃?wù)

獲取源帳號和目標(biāo)帳號,分別通知源帳號和目標(biāo)帳號進(jìn)行扣除金額和增加金額的操作;

提供返回結(jié)果給應(yīng)用層;

基礎(chǔ)層服務(wù)

按照應(yīng)用層的請求,發(fā)送Email通知;

所以,從上面的例子中可以清晰的看出,每種服務(wù)的職責(zé);

聚合及聚合根(Aggregate,Aggregate Root)

聚合,它通過定義對象之間清晰的所屬關(guān)系和邊界來實現(xiàn)領(lǐng)域模型的內(nèi)聚,并避免了錯綜復(fù)雜的難以維護(hù)的對象關(guān)系網(wǎng)的形成。聚合定義了一組具有內(nèi)聚關(guān)系的相關(guān)對象的集合,我們把聚合看作是一個修改數(shù)據(jù)的單元。

聚合有以下一些特點:

每個聚合有一個根和一個邊界,邊界定義了一個聚合內(nèi)部有哪些實體或值對象,根是聚合內(nèi)的某個實體;

聚合內(nèi)部的對象之間可以相互引用,但是聚合外部如果要訪問聚合內(nèi)部的對象時,必須通過聚合根開始導(dǎo)航,絕對不能繞過聚合根直接訪問聚合內(nèi)的對象,也就是說聚合根是外部可以保持 對它的引用的唯一元素;

聚合內(nèi)除根以外的其他實體的唯一標(biāo)識都是本地標(biāo)識,也就是只要在聚合內(nèi)部保持唯一即可,因為它們總是從屬于這個聚合的;

聚合根負(fù)責(zé)與外部其他對象打交道并維護(hù)自己內(nèi)部的業(yè)務(wù)規(guī)則;

基于聚合的以上概念,我們可以推論出從數(shù)據(jù)庫查詢時的單元也是以聚合為一個單元,也就是說我們不能直接查詢聚合內(nèi)部的某個非根的對象;

聚合內(nèi)部的對象可以保持對其他聚合根的引用;

刪除一個聚合根時必須同時刪除該聚合內(nèi)的所有相關(guān)對象,因為他們都同屬于一個聚合,是一個完整的概念;

關(guān)于如何識別聚合以及聚合根的問題:

我覺得我們可以先從業(yè)務(wù)的角度深入思考,然后慢慢分析出有哪些對象是:

有獨立存在的意義,即它是不依賴于其他對象的存在它才有意義的;

可以被獨立訪問的,還是必須通過某個其他對象導(dǎo)航得到的;

如何識別聚合?

我覺得這個需要從業(yè)務(wù)的角度深入分析哪些對象它們的關(guān)系是內(nèi)聚的,即我們會把他們看成是一個整體來考慮的;然后這些對象我們就可以把它們放在一個聚合內(nèi)。所謂關(guān)系是內(nèi)聚的,是指這些對象之間必須保持一個固定規(guī)則,固定規(guī)則是指在數(shù)據(jù)變化時必須保持不變的一致性規(guī)則。當(dāng)我們在修改一個聚合時,我們必須在事務(wù)級別確保整個聚合內(nèi)的所有對象滿足這個固定規(guī)則。作為一條建議,聚合盡量不要太大,否則即便能夠做到在事務(wù)級別保持聚合的業(yè)務(wù)規(guī)則完整性,也可能會帶來一定的性能問題。有分析報告顯示,通常在大部分領(lǐng)域模型中,有70%的聚合通常只有一個實體,即聚合根,該實體內(nèi)部沒有包含其他實體,只包含一些值對象;另外30%的聚合中,基本上也只包含兩到三個實體。這意味著大部分的聚合都只是一個實體,該實體同時也是聚合根。

如何識別聚合根?

如果一個聚合只有一個實體,那么這個實體就是聚合根;如果有多個實體,那么我們可以思考聚合內(nèi)哪個對象有獨立存在的意義并且可以和外部直接進(jìn)行交互。

工廠(Factory)

DDD中的工廠也是一種體現(xiàn)封裝思想的模式。DDD中引入工廠模式的原因是:有時創(chuàng)建一個領(lǐng)域?qū)ο笫且患容^復(fù)雜的事情,不僅僅是簡單的new操作。正如對象封裝了內(nèi)部實現(xiàn)一樣(我們無需知道對象的內(nèi)部實現(xiàn)就可以使用對象的行為),工廠則是用來封裝創(chuàng)建一個復(fù)雜對象尤其是聚合時所需的知識,工廠的作用是將創(chuàng)建對象的細(xì)節(jié)隱藏起來??蛻魝鬟f給工廠一些簡單的參數(shù),然后工廠可以在內(nèi)部創(chuàng)建出一個復(fù)雜的領(lǐng)域?qū)ο笕缓蠓祷亟o客戶。領(lǐng)域模型中其他元素都不適合做這個事情,所以需要引入這個新的模式,工廠。工廠在創(chuàng)建一個復(fù)雜的領(lǐng)域?qū)ο髸r,通常會知道該滿足什么業(yè)務(wù)規(guī)則(它知道先怎樣實例化一個對象,然后在對這個對象做哪些初始化操作,這些知識就是創(chuàng)建對象的細(xì)節(jié)),如果傳遞進(jìn)來的參數(shù)符合創(chuàng)建對象的業(yè)務(wù)規(guī)則,則可以順利創(chuàng)建相應(yīng)的對象;但是如果由于參數(shù)無效等原因不能創(chuàng)建出期望的對象時,應(yīng)該拋出一個異常,以確保不會創(chuàng)建出一個錯誤的對象。當(dāng)然我們也并不總是需要通過工廠來創(chuàng)建對象,事實上大部分情況下領(lǐng)域?qū)ο蟮膭?chuàng)建都不會太復(fù)雜,所以我們只需要簡單的使用構(gòu)造函數(shù)創(chuàng)建對象就可以了。隱藏創(chuàng)建對象的好處是顯而易見的,這樣可以不會讓領(lǐng)域?qū)拥臉I(yè)務(wù)邏輯泄露到應(yīng)用層,同時也減輕了應(yīng)用層的負(fù)擔(dān),它只需要簡單的調(diào)用領(lǐng)域工廠創(chuàng)建出期望的對象即可。

倉儲(Repository)

倉儲被設(shè)計出來的目的是基于這個原因:領(lǐng)域模型中的對象自從被創(chuàng)建出來后不會一直留在內(nèi)存中活動的,當(dāng)它不活動時會被持久化到數(shù)據(jù)庫中,然后當(dāng)需要的時候我們會重建該對象;重建對象就是根據(jù)數(shù)據(jù)庫中已存儲的對象的狀態(tài)重新創(chuàng)建對象的過程;所以,可見重建對象是一個和數(shù)據(jù)庫打交道的過程。從更廣義的角度來理解,我們經(jīng)常會像集合一樣從某個類似集合的地方根據(jù)某個條件獲取一個或一些對象,往集合中添加對象或移除對象。也就是說,我們需要提供一種機(jī)制,可以提供類似集合的接口來幫助我們管理對象。倉儲就是基于這樣的思想被設(shè)計出來的;

倉儲里面存放的對象一定是聚合,原因是之前提到的領(lǐng)域模型中是以聚合的概念去劃分邊界的;聚合是我們更新對象的一個邊界,事實上我們把整個聚合看成是一個整體概念,要么一起被取出來,要么一起被刪除。我們永遠(yuǎn)不會單獨對某個聚合內(nèi)的子對象進(jìn)行單獨查詢或做更新操作。因此,我們只對聚合設(shè)計倉儲。

倉儲還有一個重要的特征就是分為倉儲定義部分和倉儲實現(xiàn)部分,在領(lǐng)域模型中我們定義倉儲的接口,而在基礎(chǔ)設(shè)施層實現(xiàn)具體的倉儲。這樣做的原因是:由于倉儲背后的實現(xiàn)都是在和數(shù)據(jù)庫打交道,但是我們又不希望客戶(如應(yīng)用層)把重點放在如何從數(shù)據(jù)庫獲取數(shù)據(jù)的問題上,因為這樣做會導(dǎo)致客戶(應(yīng)用層)代碼很混亂,很可能會因此而忽略了領(lǐng)域模型的存在。所以我們需要提供一個簡單明了的接口,供客戶使用,確??蛻裟芤宰詈唵蔚姆绞将@取領(lǐng)域?qū)ο?,從而可以讓它專心的不會被什么?shù)據(jù)訪問代碼打擾的情況下協(xié)調(diào)領(lǐng)域?qū)ο笸瓿蓸I(yè)務(wù)邏輯。這種通過接口來隔離封裝變化的做法其實很常見。由于客戶面對的是抽象的接口并不是具體的實現(xiàn),所以我們可以隨時替換倉儲的真實實現(xiàn),這很有助于我們做單元測試。

盡管倉儲可以像集合一樣在內(nèi)存中管理對象,但是倉儲一般不負(fù)責(zé)事務(wù)處理。一般事務(wù)處理會交給一個叫“工作單元(Unit Of Work)”的東西。關(guān)于工作單元的詳細(xì)信息我在下面的討論中會講到。

另外,倉儲在設(shè)計查詢接口時,可能還會用到規(guī)格模式(Specification Pattern),我見過的最厲害的規(guī)格模式應(yīng)該就是LINQ以及DLINQ查詢了。一般我們會根據(jù)項目中查詢的靈活度要求來選擇適合的倉儲查詢接口設(shè)計。通常情況下只需要定義簡單明了的具有固定查詢參數(shù)的查詢接口就可以了。只有是在查詢條件是動態(tài)指定的情況下才可能需要用到Specification等模式。

根據(jù)需求建立一個初步的領(lǐng)域模型,識別出一些明顯的領(lǐng)域概念以及它們的關(guān)聯(lián),關(guān)聯(lián)可以暫時沒有方向但需要有(1:1,1:N,M:N)這些關(guān)系;可以用文字精確的沒有歧義的描述出每個領(lǐng)域概念的涵義以及包含的主要信息;

分析主要的軟件應(yīng)用程序功能,識別出主要的應(yīng)用層的類;這樣有助于及早發(fā)現(xiàn)哪些是應(yīng)用層的職責(zé),哪些是領(lǐng)域?qū)拥穆氊?zé);

進(jìn)一步分析領(lǐng)域模型,識別出哪些是實體,哪些是值對象,哪些是領(lǐng)域服務(wù);

分析關(guān)聯(lián),通過對業(yè)務(wù)的更深入分析以及各種軟件設(shè)計原則及性能方面的權(quán)衡,明確關(guān)聯(lián)的方向或者去掉一些不需要的關(guān)聯(lián);

找出聚合邊界及聚合根,這是一件很有難度的事情;因為你在分析的過程中往往會碰到很多模棱兩可的難以清晰判斷的選擇問題,所以,需要我們平時一些分析經(jīng)驗的積累才能找出正確的聚合根;

為聚合根配備倉儲,一般情況下是為一個聚合分配一個倉儲,此時只要設(shè)計好倉儲的接口即可;

走查場景,確定我們設(shè)計的領(lǐng)域模型能夠有效地解決業(yè)務(wù)需求;

考慮如何創(chuàng)建領(lǐng)域?qū)嶓w或值對象,是通過工廠還是直接通過構(gòu)造函數(shù);

停下來重構(gòu)模型。尋找模型中覺得有些疑問或者是蹩腳的地方,比如思考一些對象應(yīng)該通過關(guān)聯(lián)導(dǎo)航得到還是應(yīng)該從倉儲獲取?聚合設(shè)計的是否正確?考慮模型的性能怎樣,等等;

領(lǐng)域建模是一個不斷重構(gòu),持續(xù)完善模型的過程,大家會在討論中將變化的部分反映到模型中,從而是模型不斷細(xì)化并朝正確的方向走。領(lǐng)域建模是領(lǐng)域?qū)<摇⒃O(shè)計人員、開發(fā)人員之間溝通交流的過程,是大家工作和思考問題的基礎(chǔ)。

從經(jīng)典的領(lǐng)域驅(qū)動設(shè)計分層架構(gòu)中可以看出,領(lǐng)域?qū)拥纳蠈邮菓?yīng)用層,下層是基礎(chǔ)設(shè)施層。那么領(lǐng)域?qū)邮侨绾闻c其它層交互的呢?

對于會影響領(lǐng)域?qū)又蓄I(lǐng)域?qū)ο鬆顟B(tài)的應(yīng)用層功能

一般應(yīng)用層會先啟動一個工作單元,然后:

對于修改領(lǐng)域?qū)ο蟮那闆r,通過倉儲獲取領(lǐng)域?qū)ο?,調(diào)用領(lǐng)域?qū)ο蟮南嚓P(guān)業(yè)務(wù)方法以完成業(yè)務(wù)邏輯處理;

對于新增領(lǐng)域?qū)ο蟮那闆r,通過構(gòu)造函數(shù)或工廠創(chuàng)建出領(lǐng)域?qū)ο?,如果需要還可以繼續(xù)對該新創(chuàng)建的領(lǐng)域?qū)ο笞鲆恍┎僮?,然后把該新?chuàng)建的領(lǐng)域?qū)ο筇砑拥絺}儲中;

對于刪除領(lǐng)域?qū)ο蟮那闆r,可以先把領(lǐng)域?qū)ο髲膫}儲中取出來,然后將其從倉儲中刪除,也可以直接傳遞一個要刪除的領(lǐng)域?qū)ο蟮奈ㄒ粯?biāo)識給倉儲通知其移除該唯一標(biāo)識對應(yīng)領(lǐng)域?qū)ο螅?/p>

如果一個業(yè)務(wù)邏輯涉及到多個領(lǐng)域?qū)ο螅瑒t調(diào)用領(lǐng)域?qū)又械南嚓P(guān)領(lǐng)域服務(wù)完成操作;

注意,以上所說的所有領(lǐng)域?qū)ο蠖际侵痪酆细?,另外在?yīng)用層需要獲取倉儲接口以及領(lǐng)域服務(wù)接口時,都可以通過IOC容器獲取。最后通知工作單元提交事務(wù)從而將所有相關(guān)的領(lǐng)域?qū)ο蟮臓顟B(tài)以事務(wù)的方式持久化到數(shù)據(jù)庫;

關(guān)于Unit of Work(工作單元)的幾種實現(xiàn)方法

基于快照的實現(xiàn),即領(lǐng)域?qū)ο蟊蝗〕鰜砗螅瑫缺4嬉粋€備份的對象,然后當(dāng)在做持久化操作時,將最新的對象的狀態(tài)和備份的對象的狀態(tài)進(jìn)行比較,如果不相同,則認(rèn)為有做過修改,然后進(jìn)行持久化;這種設(shè)計的好處是對象不用告訴工作單元自己的狀態(tài)修改了,而缺點也是顯而易見的,那就是性能可能會低,備份對象以及比較對象的狀態(tài)是否有修改的過程在當(dāng)對象本身很復(fù)雜的時候,往往是一個比較耗時的步驟,而且要真正實現(xiàn)對象的深拷貝以及判斷屬性是否修改還是比較困難的;

不基于快照,而是倉儲的相關(guān)更新或新增或刪除接口被調(diào)用時,倉儲通知工作單元某個對象被新增了或更新了或刪除了。這樣工作單元在做數(shù)據(jù)持久化時也同樣可以知道需要持久化哪些對象了;這種方法理論上不需要ORM框架的支持,對領(lǐng)域模型也沒有任何傾入性,同時也很好的支持了工作單元的模式。對于不想用高級ORM框架的朋友來說,這種方法挺好;

不基于快照,也不用倉儲告訴工作單元數(shù)據(jù)更改了。而是采用AOP的思想,采用透明代理的方式進(jìn)行一個攔截。在NHibernate中,我們的屬性通常要被聲明為virtual的,一個原因就是NHibernate會生成一個透明代理,用于攔截對象的屬性被修改時,自動通知工作單元對象的狀態(tài)被更新了。這樣工作單元也同樣知道需要持久化哪些對象了。這種方法對領(lǐng)域模型的傾入性不大,并且能很好的支持工作單元模式,如果用NHibernate作為ORM,這種方法用的比較多;

一般是微軟用的方法,那就是讓領(lǐng)域?qū)ο髮崿F(xiàn).NET框架中的INotifiyPropertyChanged接口,然后在每個屬性的set方法的最后一行調(diào)用OnPropertyChanged的方法從而顯示地通知別人自己的狀態(tài)修改了。這種方法相對來說對領(lǐng)域模型的傾入性最強(qiáng)。

對于不會影響領(lǐng)域?qū)又蓄I(lǐng)域?qū)ο鬆顟B(tài)的查詢功能

可以直接通過倉儲查詢出所需要的數(shù)據(jù)。但一般領(lǐng)域?qū)又械膫}儲提供的查詢功能也許不能滿足界面顯示的需要,則可能需要多次調(diào)用不同的倉儲才能獲取所需要顯示的數(shù)據(jù);其實針對這種查詢的情況,我在后面會講到可以直接通過CQRS的架構(gòu)來實現(xiàn)。即對于查詢,我們可以在應(yīng)用層不調(diào)用領(lǐng)域?qū)拥娜魏螙|西,而是直接通過某個其他的用另外的技術(shù)架構(gòu)實現(xiàn)的查詢引擎來完成查詢,比如直接通過構(gòu)造參數(shù)化SQL的方式從數(shù)據(jù)庫一個表或多個表中查詢出任何想要顯示的數(shù)據(jù)。這樣不僅性能高,也可以減輕領(lǐng)域?qū)拥呢?fù)擔(dān)。領(lǐng)域模型不太適合為應(yīng)用層提供各種查詢服務(wù),因為往往界面上要顯示的數(shù)據(jù)是很多對象的組合信息,是一種非對象概念的信息,就像報表;

對象將需求用類一個個隔開,就像用儲物箱把東西一個個封裝起來一樣,需求變了,分幾種情況,最嚴(yán)重的是大變,那么每個儲物箱都要打開改,這種方法就不見得有好處;但是這種情況發(fā)生概率比較小,大部分需求變化都是局限在一兩個儲物箱中,那么我們只要打開這兩個儲物箱修改就可以,不會影響其他儲物柜了。

而面向過程是把所有東西都放在一個大儲物箱中,修改某個部分以后,會引起其他部分不穩(wěn)定,一個BUG修復(fù),引發(fā)新的無數(shù)BUG,最后程序員陷入焦頭爛額,如日本東京電力公司員工處理核危機(jī)一樣,心力交瘁啊。

所以,我們不能粗粒度看需求變,認(rèn)為需求變了,就是大范圍變,萬事萬物都有邊界,老子說,無欲觀其繳,什么事物都要觀察其邊界,雖然需求可以用“需求”這個名詞表達(dá),談到需求變了,不都意味著最大邊界范圍的變化,這樣看問題容易走極端。

其實就是就地畫圈圈——邊界。我們小時候?qū)懽魑姆掷先我彩峭瑯拥览恚髯月氊?zé)明確,劃分邊界明確,通過過渡句實現(xiàn)承上啟下——接口。為什么組織需要分不同部門,同樣是邊界思維。畫圈圈容易,但如何畫才難,所以O(shè)O中思維非常重要。

需求變化所引起的變化是有邊界,若果變化的邊界等于整個領(lǐng)域,那么已經(jīng)是完全不同的項目了。要掌握邊界,是需要大量的領(lǐng)域知識的。否則,走進(jìn)銀行連業(yè)務(wù)職責(zé)都分不清的,如何畫圈圈呢?

面向過程是無邊界一詞的(就算有也只是最大的邊界),它沒有要求各自獨立,它可以橫跨邊界進(jìn)行調(diào)用,這就是容易引起B(yǎng)UG的原因,引起B(yǎng)UG不一定是技術(shù)錯誤,更多的是邏輯錯誤。分別封裝就是畫圈圈了,所有邊界都以接口實現(xiàn)。不用改或者小改接口,都不會牽一發(fā)動全身。若果面向過程中考慮邊界,那么也就已經(jīng)上升到OO思維,即使用的不是對象語言,但對象已經(jīng)隱含其中。說白了,面向?qū)ο笈c面向過程最大區(qū)別就是:分解。邊界的分解。從需求到最后實現(xiàn)都貫穿。

面向?qū)ο蟮膶嵸|(zhì)就是邊界劃分,封裝,不但對需求變化能夠量化,縮小影響面;因為邊界劃分也會限制出錯的影響范圍,所以O(shè)O對軟件后期BUG等出錯也有好處。

軟件世界永遠(yuǎn)都有BUG,BUG是清除不干凈的,就像人類世界永遠(yuǎn)都存在不完美和陰暗面,問題關(guān)鍵是:上帝用空間和時間的邊界把人類世界痛苦災(zāi)難等不完美局限在一個范圍內(nèi);而軟件世界如果你不采取OO等方法進(jìn)行邊界劃分的話,一旦出錯,追查起來情況會有多糟呢?

軟件世界其實類似人類現(xiàn)實世界,有時出問題了,探究原因一看,原來是兩個看上去毫無聯(lián)系的因素導(dǎo)致的,古人只好經(jīng)常求神拜佛,我們程序員在自己的軟件上線運行時,大概心里也在求神拜佛別出大紕漏,如果我們的軟件采取OO封裝,我們就會坦然些,肯定會出錯,但是我們已經(jīng)預(yù)先劃定好邊界,所以,不會產(chǎn)生嚴(yán)重后果,甚至也不會出現(xiàn)難以追查的魔鬼BUG。

上面只是涉及到DDD中最基本的內(nèi)容,DDD中還有很多其他重要的內(nèi)容在上面沒有提到,如:

模型上下文、上下文映射、上下文共享;

如何將分析模式和設(shè)計模式運用到DDD中;

一些關(guān)于柔性設(shè)計的技巧;

如果保持模型完整性,以及持續(xù)集成方面的知識;

如何精煉模型,識別核心模型以及通用子領(lǐng)域;

這些主題都很重要,因為篇幅有限以及我目前掌握的知識也有限,并且為了突出這篇文章的重點,所以不對他們做詳細(xì)介紹了,大家有興趣的可以自己閱讀一下。

CQRS架構(gòu)

核心思想是將應(yīng)用程序的查詢部分和命令部分完全分離,這兩部分可以用完全不同的模型和技術(shù)去實現(xiàn)。比如命令部分可以通過領(lǐng)域驅(qū)動設(shè)計來實現(xiàn);查詢部分可以直接用最快的非面向?qū)ο蟮姆绞饺崿F(xiàn),比如用SQL。這樣的思想有很多好處:

實現(xiàn)命令部分的領(lǐng)域模型不用經(jīng)常為了領(lǐng)域?qū)ο罂赡軙蝗绾尾樵兌鲆恍┱壑刑幚恚?/p>

由于命令和查詢是完全分離的,所以這兩部分可以用不同的技術(shù)架構(gòu)實現(xiàn),包括數(shù)據(jù)庫設(shè)計都可以分開設(shè)計,每一部分可以充分發(fā)揮其長處;

高性能,命令端因為沒有返回值,可以像消息隊列一樣接受命令,放在隊列中,慢慢處理;處理完后,可以通過異步的方式通知查詢端,這樣查詢端可以做數(shù)據(jù)同步的處理;

Event Sourcing(事件溯源)

對于DDD中的聚合,不保存聚合的當(dāng)前狀態(tài),而是保存對象上所發(fā)生的每個事件。當(dāng)要重建一個聚合對象時,可以通過回溯這些事件(即讓這些事件重新發(fā)生)來讓對象恢復(fù)到某個特定的狀態(tài);因為有時一個聚合可能會發(fā)生很多事件,所以如果每次要在重建對象時都從頭回溯事件,會導(dǎo)致性能低下,所以我們會在一定時候為聚合創(chuàng)建一個快照。這樣,我們就可以基于某個快照開始創(chuàng)建聚合對象了。

DCI架構(gòu)

DCI架構(gòu)強(qiáng)調(diào),軟件應(yīng)該真實的模擬現(xiàn)實生活中對象的交互方式,代碼應(yīng)該準(zhǔn)確樸實的反映用戶的心智模型。在DCI中有:數(shù)據(jù)模型、角色模型、以及上下文這三個概念。數(shù)據(jù)模型表示程序的結(jié)構(gòu),目前我們所理解的DDD中的領(lǐng)域模型可以很好的表示數(shù)據(jù)模型;角色模型表示數(shù)據(jù)如何交互,一個角色定義了某個“身份”所具有的交互行為;上下文對應(yīng)業(yè)務(wù)場景,用于實現(xiàn)業(yè)務(wù)用例,注意是業(yè)務(wù)用例而不是系統(tǒng)用例,業(yè)務(wù)用例只與業(yè)務(wù)相關(guān);軟件運行時,根據(jù)用戶的操作,系統(tǒng)創(chuàng)建相應(yīng)的場景,并把相關(guān)的數(shù)據(jù)對象作為場景參與者傳遞給場景,然后場景知道該為每個對象賦予什么角色,當(dāng)對象被賦予某個角色后就真正成為有交互能力的對象,然后與其他對象進(jìn)行交互;這個過程與現(xiàn)實生活中我們所理解的對象是一致的;

DCI的這種思想與DDD中的領(lǐng)域服務(wù)所做的事情是一樣的,但實現(xiàn)的角度有些不同。DDD中的領(lǐng)域服務(wù)被創(chuàng)建的出發(fā)點是當(dāng)一些職責(zé)不太適合放在任何一個領(lǐng)域?qū)ο笊蠒r,這個職責(zé)往往對應(yīng)領(lǐng)域中的某個活動或轉(zhuǎn)換過程,此時我們應(yīng)該考慮將其放在一個服務(wù)中。比如資金轉(zhuǎn)帳的例子,我們應(yīng)該提供一個資金轉(zhuǎn)帳的服務(wù),用來對應(yīng)領(lǐng)域中的資金轉(zhuǎn)帳這個領(lǐng)域概念。但是領(lǐng)域服務(wù)內(nèi)部做的事情是協(xié)調(diào)多個領(lǐng)域?qū)ο笸瓿梢患虑?。因此,在DDD中的領(lǐng)域服務(wù)在協(xié)調(diào)領(lǐng)域?qū)ο笞鍪虑闀r,領(lǐng)域?qū)ο笸翘幱谝粋€被動的地位,領(lǐng)域服務(wù)通知每個對象要求其做自己能做的事情,這樣就行了。這個過程中我們似乎看不到對象之間交互的意思,因為整個過程都是由領(lǐng)域服務(wù)以面向過程的思維去實現(xiàn)了。而DCI則通用引入角色,賦予角色以交互能力,然后讓角色之間進(jìn)行交互,從而可以讓我們看到對象與對象之間交互的過程。但前提是,對象之間確實是在交互。因為現(xiàn)實生活中并不是所有的對象在做交互,比如有A、B、C三個對象,A通知B做事情,A通知C做事情,此時可以認(rèn)為A和B,A和C之間是在交互,但是B和C之間沒有交互。所以我們需要分清這種情況。資金轉(zhuǎn)帳的例子,A相當(dāng)于轉(zhuǎn)帳服務(wù),B相當(dāng)于帳號1,C相當(dāng)于帳號2。因此,資金轉(zhuǎn)帳這個業(yè)務(wù)場景,用領(lǐng)域服務(wù)比較自然。有人認(rèn)為DCI可以替換DDD中的領(lǐng)域服務(wù),我持懷疑態(tài)度。

四色原型分析模式

時刻-時間段原型(Moment-Interval Archetype)

表示在某個時刻或某一段時間內(nèi)發(fā)生的某個活動。使用粉紅色表示,簡寫為MI。

參與方-地點-物品原型(Part-Place-Thing Archetype)

表示參與某個活動的人或物,地點則是活動的發(fā)生地。使用綠色表示。簡寫為PPT。

描述原型(Description Archetype)

表示對PPT的本質(zhì)描述。它不是PPT的分類!Description是從PPT抽象出來的不變的共性的屬性的集合。使用藍(lán)色表示,簡寫為DESC。

舉個例子,有一個人叫張三,如果某個外星人問你張三是什么?你會怎么說?可能會說,張三是個人,但是外星人不知道“人”是什么。然后你會怎么辦?你就會說:張三是個由一個頭、兩只手、兩只腳,以及一個身體組成的客觀存在。雖然這時外星人仍然不知道人是什么,但我已經(jīng)可以借用這個例子向大家說明什么是“Description”了。在這個例子中,張三就是一個PPT,而“由一個頭、兩只手、兩只腳,以及一個身體組成的客觀存在”就是對張三的Description,頭、手、腳、身體則是人的本質(zhì)的不變的共性的屬性的集合。但我們?nèi)祟惐容^聰明,很會抽象總結(jié)和命名,已經(jīng)把這個Description用一個字來代替了,那就是“人”。所以就有所謂的張三是人的說法。

角色原型(Role Archetype)

角色就是我們平時所理解的“身份”。使用黃色表示,簡寫為Role。為什么會有角色這個概念?因為有些活動,只允許具有特定角色(身份)的PPT(參與者)才能參與該活動。比如一個人只有具有教師的角色才能上課(一種活動);一個人只有是一個合法公民才能參與選舉和被選舉;但是有些活動也是不需要角色的,比如一個人不需要具備任何角色就可以睡覺(一種活動)。當(dāng)然,其實說人不需要角色就能睡覺也是錯誤的,錯在哪里?因為我們可以這樣理解:一個客觀存在只要具有“人”的角色就能睡覺,其實這時候,我們已經(jīng)把DESC當(dāng)作角色來看待了。所以,其實角色這個概念是非常廣的,不能用我們平時所理解的狹義的“身份”來理解,因為“教師”、“合法公民”、“人”都可以被作為角色來看待。因此,應(yīng)該這樣說:任何一個活動,都需要具有一定角色的參與者才能參與。

用一句話來概括四色原型就是:一個什么什么樣的人或組織或物品以某種角色在某個時刻或某段時間內(nèi)參與某個活動。其中“什么什么樣的”就是DESC,“人或組織或物品”就是PPT,“角色”就是Role,而”某個時刻或某段時間內(nèi)的某個活動"就是MI。

以上這些東西如果在學(xué)習(xí)了DDD之后再去學(xué)習(xí)會對DDD有更深入的了解,但我覺得DDD相對比較基礎(chǔ),如果我們在已經(jīng)了解了DDD的基礎(chǔ)之上再去學(xué)習(xí)這些東西會更加有效和容易掌握。





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • XML
    XML
    +關(guān)注

    關(guān)注

    0

    文章

    186

    瀏覽量

    32993
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7575

    瀏覽量

    134052
  • 驅(qū)動設(shè)計
    +關(guān)注

    關(guān)注

    1

    文章

    109

    瀏覽量

    15235
  • ddd
    ddd
    +關(guān)注

    關(guān)注

    0

    文章

    21

    瀏覽量

    2889

原文標(biāo)題:一文理解 DDD 領(lǐng)域驅(qū)動設(shè)計!

文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    文理解BOOST電路的升壓原理

      BOOST電路,個作為硬件工程師熟悉的不能再熟悉的硬件電路。其在很多硬件領(lǐng)域都會運用到。
    發(fā)表于 11-08 16:03 ?1.6w次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文理解</b>BOOST電路的升壓原理

    文理解示波器帶寬

    當(dāng)示波器用戶選擇示波器進(jìn)行關(guān)鍵的測量時,示波器的主要參數(shù)指標(biāo)往往是選擇哪款示波器的唯標(biāo)準(zhǔn)。
    的頭像 發(fā)表于 11-06 10:37 ?1580次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文理解</b>示波器帶寬

    文理解自舉電路原理

    我們從最簡單的電路開始點分析,先定義下輸入阻抗的計算過程。我們可以粗略的把負(fù)載作為個黑盒子來對待,所謂的輸入阻抗,就是計算輸入到這個黑盒子的電壓與電流的比值,比如下圖,輸入阻
    的頭像 發(fā)表于 12-18 09:24 ?1158次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文理解</b>自舉電路原理

    黑客攻防入門與進(jìn)階ddd

    黑客攻防入門與進(jìn)階ddd黑客攻防入門與進(jìn)階ddd
    發(fā)表于 02-23 15:45 ?8次下載

    詳解領(lǐng)域驅(qū)動設(shè)計和spring

    領(lǐng)域驅(qū)動設(shè)計 Eric Evans的《領(lǐng)域驅(qū)動設(shè)計》無疑是軟件設(shè)計領(lǐng)域最重要的幾本書之。 這本
    發(fā)表于 09-27 12:51 ?0次下載
    詳解<b class='flag-5'>領(lǐng)域</b><b class='flag-5'>驅(qū)動</b>設(shè)計和spring

    詳解領(lǐng)域驅(qū)動設(shè)計和spring

    領(lǐng)域驅(qū)動設(shè)計 Eric Evans的《領(lǐng)域驅(qū)動設(shè)計》無疑是軟件設(shè)計領(lǐng)域最重要的幾本書之。 這本
    發(fā)表于 09-27 12:51 ?0次下載
    詳解<b class='flag-5'>領(lǐng)域</b><b class='flag-5'>驅(qū)動</b>設(shè)計和spring

    文理清EMI的傳播過程資料下載

    電子發(fā)燒友網(wǎng)為你提供文理清EMI的傳播過程資料下載的電子資料下載,更有其他相關(guān)的電路圖、源代碼、課件教程、中文資料、英文資料、參考設(shè)計、用戶指南、解決方案等資料,希望可以幫助到廣大的電子工程師們。
    發(fā)表于 04-04 08:52 ?5次下載
    <b class='flag-5'>一</b><b class='flag-5'>文理</b>清EMI的傳播過程資料下載

    文理解 Redis 的核心原理與技術(shù)

    、Redis 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu) 1. String Redis 里的字符串是動態(tài)字符串,會根據(jù)實際情況動態(tài)調(diào)整。類似于 Go 里面的切片-slice,如果長度不夠則自動擴(kuò)容。至于如何擴(kuò)容,方法大致如下
    的頭像 發(fā)表于 05-28 10:49 ?1497次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文理解</b> Redis 的核心原理與技術(shù)

    文理解python模塊的緩存

    個模塊內(nèi)部重復(fù)引用另個相同模塊,實際并不會導(dǎo)入兩次,原因是在使用關(guān)鍵字 import 導(dǎo)入模塊時,它會先檢索 sys.modules 里是否已經(jīng)載入這個模塊了,如果已經(jīng)載入,則不會再次導(dǎo)入,如果不存在,才會去檢索導(dǎo)入這個模塊。
    的頭像 發(fā)表于 03-14 16:42 ?1724次閱讀

    文理解相位補償

    幫朋友做鎳氫充電器,利用鎳氫電池充滿電時電壓有個微小的下降這個特點來識別是否已經(jīng)充滿,比如1.2V的鎳氫電池,快充滿的時候,電壓在1.35V,之后逐步下降,電壓可以低于1.30V。所以需要單片機(jī)間歇檢測電池兩端電壓,大概充3秒鐘電再停止,之后檢測電池兩端電壓。
    的頭像 發(fā)表于 02-10 15:17 ?1642次閱讀

    用好DDD必須先過Spring Data這關(guān)

    DDD領(lǐng)域驅(qū)動的設(shè)計方法,旨在通過建立對領(lǐng)域模型的清晰理解來解決業(yè)務(wù)問題。和事務(wù)腳本不同
    的頭像 發(fā)表于 03-07 09:38 ?2005次閱讀

    文理解PID算法

    在直流有刷電機(jī)的基礎(chǔ)驅(qū)動中,如果電機(jī)負(fù)載不變,我們只要設(shè)置固定的占空比(電壓),電機(jī)的速度就會穩(wěn)定在目標(biāo)范圍。
    的頭像 發(fā)表于 05-06 15:08 ?2607次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文理解</b>PID算法

    DDD驅(qū)動如何設(shè)計?如何進(jìn)行領(lǐng)域建模?

    DDD是Eric Evans在2003年出版的《領(lǐng)域驅(qū)動設(shè)計:軟件核心復(fù)雜性應(yīng)對之道》(Domain-Driven Design: Tackling Complexity in the Heart
    的頭像 發(fā)表于 07-18 14:11 ?1000次閱讀
    <b class='flag-5'>DDD</b><b class='flag-5'>驅(qū)動</b>如何設(shè)計?如何進(jìn)行<b class='flag-5'>領(lǐng)域</b>建模?

    文理解IGBT的發(fā)展歷程

    它在半導(dǎo)體領(lǐng)域嶄露頭角,成為現(xiàn)代技術(shù)發(fā)展不可或缺的重要組成部分。為了更好地理解IGBT的發(fā)展歷程,讓我們深入探索這激動人心的故事。
    發(fā)表于 09-05 10:15 ?1054次閱讀

    DDD是什么?DDD核心概念梳理

    DDD 是什么,DDD 的英文全稱是 Domain-Driven Design,翻譯過來就是領(lǐng)域驅(qū)動設(shè)計。
    的頭像 發(fā)表于 09-07 11:12 ?7363次閱讀
    <b class='flag-5'>DDD</b>是什么?<b class='flag-5'>DDD</b>核心概念梳理