版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
一文理解DDD領(lǐng)域驅(qū)動設計!2004年EricEvans發(fā)表Domain-DrivenDesign–TacklingComplexityintheHeartofSoftware(領(lǐng)域驅(qū)動設計),簡稱EvansDDD。領(lǐng)域驅(qū)動設計分為兩個階段:以一種領(lǐng)域?qū)<?、設計人員、開發(fā)人員都能理解的通用語言作為相互交流的工具,在交流的過程中發(fā)現(xiàn)領(lǐng)域概念,然后將這些概念設計成一個領(lǐng)域模型;由領(lǐng)域模型驅(qū)動軟件設計,用代碼來實現(xiàn)該領(lǐng)域模型;由此可見,領(lǐng)域驅(qū)動設計的核心是建立正確的領(lǐng)域模型。領(lǐng)域驅(qū)動設計告訴我們,在通過軟件實現(xiàn)一個業(yè)務系統(tǒng)時,建立一個領(lǐng)域模型是非常重要和必要的,因為領(lǐng)域模型具有以下特點:領(lǐng)域模型是對具有某個邊界的領(lǐng)域的一個抽象,反映了領(lǐng)域內(nèi)用戶業(yè)務需求的本質(zhì);領(lǐng)域模型是有邊界的,只反應了我們在領(lǐng)域內(nèi)所關(guān)注的部分;領(lǐng)域模型只反映業(yè)務,和任何技術(shù)實現(xiàn)無關(guān);領(lǐng)域模型不僅能反映領(lǐng)域中的一些實體概念,如貨物,書本,應聘記錄,地址,等;還能反映領(lǐng)域中的一些過程概念,如資金轉(zhuǎn)賬,等;領(lǐng)域模型確保了我們的軟件的業(yè)務邏輯都在一個模型中,都在一個地方;這樣對提高軟件的可維護性,業(yè)務可理解性以及可重用性方面都有很好的幫助;領(lǐng)域模型能夠幫助開發(fā)人員相對平滑地將領(lǐng)域知識轉(zhuǎn)化為軟件構(gòu)造;領(lǐng)域模型貫穿軟件分析、設計,以及開發(fā)的整個過程;領(lǐng)域?qū)<?、設計人員、開發(fā)人員通過領(lǐng)域模型進行交流,彼此共享知識與信息;因為大家面向的都是同一個模型,所以可以防止需求走樣,可以讓軟件設計開發(fā)人員做出來的軟件真正滿足需求;要建立正確的領(lǐng)域模型并不簡單,需要領(lǐng)域?qū)<?、設計、開發(fā)人員積極溝通共同努力,然后才能使大家對領(lǐng)域的認識不斷深入,從而不斷細化和完善領(lǐng)域模型;為了讓領(lǐng)域模型看的見,我們需要用一些方法來表示它;圖是表達領(lǐng)域模型最常用的方式,但不是唯一的表達方式,代碼或文字描述也能表達領(lǐng)域模型;領(lǐng)域模型是整個軟件的核心,是軟件中最有價值和最具競爭力的部分;設計足夠精良且符合業(yè)務需求的領(lǐng)域模型能夠更快速的響應需求變化;我們認識到由軟件專家和領(lǐng)域?qū)<彝献鏖_發(fā)出一個領(lǐng)域的模型是絕對需要的,但是,那種方法通常會由于一些基礎交流的障礙而存在難點。開發(fā)人員滿腦子都是類、方法、算法、模式、架構(gòu),等等,總是想將實際生活中的概念和程序工件進行對應。他們希望看到要建立哪些對象類,要如何對對象類之間的關(guān)系建模。他們會習慣按照封裝、繼承、多態(tài)等面向?qū)ο缶幊讨械母拍钊ニ伎?,會隨時隨地這樣交談,這對他們來說這太正常不過了,開發(fā)人員就是開發(fā)人員。但是領(lǐng)域?qū)<彝ǔ@一無所知,他們對軟件類庫、框架、持久化甚至數(shù)據(jù)庫沒有什么概念。他們只了解他們特有的領(lǐng)域?qū)I(yè)技能。比如,在空中交通監(jiān)控樣例中,領(lǐng)域?qū)<抑里w機、路線、海拔、經(jīng)度、緯度,知道飛機偏離了正常路線,知道飛機的發(fā)射。他們用他們自己的術(shù)語討論這些事情,有時這對于外行來說很難直接理解。如果一個人說了什么事情,其他的人不能理解,或者更糟的是錯誤理解成其他事情,又有什么機會來保證項目成功呢?在交流的過程中,需要做翻譯才能讓其他的人理解這些概念。開發(fā)人員可能會努力使用外行人的語言來解析一些設計模式,但這并一定都能成功奏效。領(lǐng)域?qū)<乙部赡軙?chuàng)建一種新的行話以努力表達他們的這些想法。在這個痛苦的交流過程中,這種類型的翻譯并不能對知識的構(gòu)建過程產(chǎn)生幫助。領(lǐng)域驅(qū)動設計的一個核心的原則是使用一種基于模型的語言。因為模型是軟件滿足領(lǐng)域的共同點,它很適合作為這種通用語言的構(gòu)造基礎。使用模型作為語言的核心骨架,要求團隊在進行所有的交流是都使用一致的語言,在代碼中也是這樣。在共享知識和推敲模型時,團隊會使用演講、文字和圖形。這兒需要確保團隊使用的語言在所有的交流形式中看上去都是一致的,這種語言被稱為“通用語言(UbiquitousLanguage)”。通用語言應該在建模過程中廣泛嘗試以推動軟件專家和領(lǐng)域?qū)<抑g的溝通,從而發(fā)現(xiàn)要在模型中使用的主要的領(lǐng)域概念。擁有一個看上去正確的模型不代表模型能被直接轉(zhuǎn)換成代碼,也或者它的實現(xiàn)可能會違背某些我們所不建議的軟件設計原則。我們該如何實現(xiàn)從模型到代碼的轉(zhuǎn)換,并讓代碼具有可擴展性、可維護性,高性能等指標呢?另外,如實反映領(lǐng)域的模型可能會導致對象持久化的一系列問題,或者導致不可接受的性能問題。那么我們應該怎么做呢?我們應該緊密關(guān)聯(lián)領(lǐng)域建模和設計,緊密將領(lǐng)域模型和軟件編碼實現(xiàn)捆綁在一起,模型在構(gòu)建時就考慮到軟件和設計。開發(fā)人員會被加入到建模的過程中來。主要的想法是選擇一個能夠恰當在軟件中表現(xiàn)的模型,這樣設計過程會很順暢并且基于模型。代碼和其下的模型緊密關(guān)聯(lián)會讓代碼更有意義并與模型更相關(guān)。有了開發(fā)人員的參與就會有反饋。它能保證模型被實現(xiàn)成軟件。如果其中某處有錯誤,會在早期就被標識出來,問題也會容易修正。寫代碼的人會很好地了解模型,會感覺自己有責任保持它的完整性。他們會意識到對代碼的一個變更其實就隱含著對模型的變更,另外,如果哪里的代碼不能表現(xiàn)原始模型的話,他們會重構(gòu)代碼。如果分析人員從實現(xiàn)過程中分離出去,他會不再關(guān)心開發(fā)過程中引入的局限性。最終結(jié)果是模型不再實用。任何技術(shù)人員想對模型做出貢獻必須花費一些時間來接觸代碼,無論他在項目中擔負的是什么主要角色。任何一個負責修改代碼的人都必須學會用代碼表現(xiàn)模型。每位開發(fā)人員都必須參與到一定級別的領(lǐng)域討論中并和領(lǐng)域?qū)<衣?lián)絡。“用戶需求”不能等同于“用戶”,捕捉“用戶心中的模型”也不能等同于“以用戶為核心設計領(lǐng)域模型”?!独献印窌杏袀€觀點:有之以為利,無之以為用。在這里,有之利,即建立領(lǐng)域模型;無之用,即包容用戶需求。舉些例子,一個杯子要裝滿一杯水,我們在制作杯子時,制作的是空杯子,即要把水倒出來,之后才能裝下水;再比如,一座房子要住人,我們在建造房子時,建造的房子是空的,唯有空的才能容納人的居住。因此,建立領(lǐng)域模型時也要將用戶置于模型之外,這樣才能包容用戶的需求。所以,我的理解是:我們設計領(lǐng)域模型時不能以用戶為中心作為出發(fā)點去思考問題,不能老是想著用戶會對系統(tǒ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)就是一個人機交互的系統(tǒng),都是以人為主的活動記錄或跟蹤;比如:論壇中如果以人為主導,那么領(lǐng)域模型就是:人發(fā)帖,人回帖,人結(jié)貼,等等;DDD的例子中,如果是以人為中心的話,就變成了:托運人托運貨物,收貨人收貨物,付款人付款,等等;因此,當我們談及領(lǐng)域模型時,已經(jīng)默認把人的因素排除開了,因為領(lǐng)域只有對人來說才有意義,人是在領(lǐng)域范圍之外的,如果人也劃入領(lǐng)域,領(lǐng)域模型將很難保持客觀性。領(lǐng)域模型是與誰用和怎樣用是無關(guān)的客觀模型。歸納起來說就是,領(lǐng)域建模是建立虛擬模型讓我們現(xiàn)實的人使用,而不是建立虛擬空間,去模仿現(xiàn)實。以EricEvans(DDD之父)在他的書中的一個貨物運輸系統(tǒng)為例子簡單說明一下。在經(jīng)過一些用戶需求討論之后,在用戶需求相對明朗之后,Eric這樣描述領(lǐng)域模型:一個Cargo(貨物)涉及多個Customer(客戶,如托運人、收貨人、付款人),每個Customer承擔不同的角色;Cargo的運送目標已指定,即Cargo有一個運送目標;由一系列滿足Specification(規(guī)格)的CarrierMovement(運輸動作)來完成運輸目標;從上面的描述我們可以看出,他完全沒有從用戶的角度去描述領(lǐng)域模型,而是以領(lǐng)域內(nèi)的相關(guān)事物為出發(fā)點,考慮這些事物的本質(zhì)關(guān)聯(lián)及其變化規(guī)律的。上述這段描述完全以貨物為中心,把客戶看成是貨物在某個場景中可能會涉及到的關(guān)聯(lián)角色,如貨物會涉及到托運人、收貨人、付款人;貨物有一個確定的目標,貨物會經(jīng)過一系列列的運輸動作到達目的地;其實,我覺得以用戶為中心來思考領(lǐng)域模型的思維只是停留在需求的表面,而沒有挖掘出真正的需求的本質(zhì);我們在做領(lǐng)域建模時需要努力挖掘用戶需求的本質(zhì),這樣才能真正實現(xiàn)用戶需求;關(guān)于用戶、參與者這兩個概念的區(qū)分,可以看一下下面的例子:試想兩個人共同玩足球游戲,操(用戶)是驅(qū)動者,它驅(qū)使足球比賽領(lǐng)域中,各個“人”(參與者)的活動。這里立下一個假設,假設操A操作某一隊員a,而隊員a擁有著某人B的信息,那么有以下說法,a是B的鏡像,a是領(lǐng)域參與者,A是驅(qū)動者。image.png用戶界面/展現(xiàn)層負責向用戶展現(xiàn)信息以及解釋用戶命令。更細的方面來講就是:請求應用層以獲取用戶所需要展現(xiàn)的數(shù)據(jù);發(fā)送命令給應用層要求其執(zhí)行某個用戶命令;應用層很薄的一層,定義軟件要完成的所有任務。對外為展現(xiàn)層提供各種應用功能(包括查詢或命令),對內(nèi)調(diào)用領(lǐng)域?qū)樱I(lǐng)域?qū)ο蠡蝾I(lǐng)域服務)完成各種業(yè)務邏輯,應用層不包含業(yè)務邏輯。領(lǐng)域?qū)迂撠煴磉_業(yè)務概念,業(yè)務狀態(tài)信息以及業(yè)務規(guī)則,領(lǐng)域模型處于這一層,是業(yè)務軟件的核心?;A設施層本層為其他層提供通用的技術(shù)能力;提供了層間的通信;為領(lǐng)域?qū)訉崿F(xiàn)持久化機制;總之,基礎設施層可以通過架構(gòu)和框架來支持其他層的技術(shù)需求;所有模式的總攬圖關(guān)聯(lián)的設計關(guān)聯(lián)本身不是一個模式,但它在領(lǐng)域建模的過程中非常重要,所以需要在探討各種模式之前,先討論一下對象之間的關(guān)聯(lián)該如何設計。我覺得對象的關(guān)聯(lián)的設計可以遵循如下的一些原則:關(guān)聯(lián)盡量少,對象之間的復雜的關(guān)聯(lián)容易形成對象的關(guān)系網(wǎng),這樣對于我們理解和維護單個對象很不利,同時也很難劃分對象與對象之間的邊界;另外,同時減少關(guān)聯(lián)有助于簡化對象之間的遍歷;對多的關(guān)聯(lián)也許在業(yè)務上是很自然的,通常我們會用一個集合來表示1對多的關(guān)系。但我們往往也需要考慮到性能問題,尤其是當集合內(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)域中需要唯一標識的領(lǐng)域概念。因為我們有時需要區(qū)分是哪個實體。有兩個實體,如果唯一標識不一樣,那么即便實體的其他所有屬性都一樣,我們也認為他們兩個不同的實體;因為實體有生命周期,實體從被創(chuàng)建后可能會被持久化到數(shù)據(jù)庫,然后某個時候又會被取出來。所以,如果我們不為實體定義一種可以唯一區(qū)分的標識,那我們就無法區(qū)分到底是這個實體還是哪個實體。另外,不應該給實體定義太多的屬性或行為,而應該尋找關(guān)聯(lián),發(fā)現(xiàn)其他一些實體或值對象,將屬性或行為轉(zhuǎn)移到其他關(guān)聯(lián)的實體或值對象上。比如Customer實體,他有一些地址信息,由于地址信息是一個完整的有業(yè)務含義的概念,所以,我們可以定義一個Address對象,然后把Customer的地址相關(guān)的信息轉(zhuǎn)移到Address對象上。如果沒有Address對象,而把這些地址信息直接放在Customer對象上,并且如果對于一些其他的類似Address的信息也都直接放在Customer上,會導致Customer對象很混亂,結(jié)構(gòu)不清晰,最終導致它難以維護和理解;搜索公眾號后端架構(gòu)師后臺回復“架構(gòu)整潔”,獲取一份驚喜禮包。值對象(ValueObject)在領(lǐng)域中,并不是沒一個事物都必須有一個唯一標識,也就是說我們不關(guān)心對象是哪個,而只關(guān)心對象是什么。就以上面的地址對象Address為例,如果有兩個Customer的地址信息是一樣的,我們就會認為這兩個Customer的地址是同一個。也就是說只要地址信息一樣,我們就認為是同一個地址。用程序的方式來表達就是,如果兩個對象的所有的屬性的值都相同我們會認為它們是同一個對象的話,那么我們就可以把這種對象設計為值對象。因此,值對象沒有唯一標識,這是它和實體的最大不同。另外值對象在判斷是否是同一個對象時是通過它們的所有屬性是否相同,如果相同則認為是同一個值對象;而我們在區(qū)分是否是同一個實體時,只看實體的唯一標識是否相同,而不管實體的屬性是否相同;值對象另外一個明顯的特征是不可變,即所有屬性都是只讀的。因為屬性是只讀的,所以可以被安全的共享;當共享值對象時,一般有復制和共享兩種做法,具體采用哪種做法還要根據(jù)實際情況而定;另外,我們應該給值對象設計的盡量簡單,不要讓它引用很多其他的對象,因為他只是一個值,就像inta=3;那么”3”就是一個我們傳統(tǒng)意義上所說的值,而值對象其實也可以和這里的”3”一樣理解,也是一個值,只不過是用對象來表示。所以,當我們在C#語言中比較兩個值對象是否相等時,會重寫GetHashCode和Equals這兩個方法,目的就是為了比較對象的值;值對象雖然是只讀的,但是可以被整個替換掉。就像你把a的值修改為”4”(a=4;)一樣,直接把”3”這個值替換為”4”了。值對象也是一樣,當你要修改Customer的Address對象引用時,不是通過Customer.Address.Street這樣的方式來實現(xiàn),因為值對象是只讀的,它是一個完整的不可分割的整體。我們可以這樣做:Customer.Address=newAddress(…);領(lǐng)域服務(DomainService)領(lǐng)域中的一些概念不太適合建模為對象,即歸類到實體對象或值對象,因為它們本質(zhì)上就是一些操作,一些動作,而不是事物。這些操作或動作往往會涉及到多個領(lǐng)域?qū)ο螅⑶倚枰獏f(xié)調(diào)這些領(lǐng)域?qū)ο蠊餐瓿蛇@個操作或動作。如果強行將這些操作職責分配給任何一個對象,則被分配的對象就是承擔一些不該承擔的職責,從而會導致對象的職責不明確很混亂。但是基于類的面向?qū)ο笳Z言規(guī)定任何屬性或行為都必須放在對象里面。所以我們需要尋找一種新的模式來表示這種跨多個對象的操作,DDD認為服務是一個很自然的范式用來對應這種跨多個對象的操作,所以就有了領(lǐng)域服務這個模式。和領(lǐng)域?qū)ο蟛煌?,領(lǐng)域服務是以動詞開頭來命名的,比如資金轉(zhuǎn)帳服務可以命名為MoneyTransferService。當然,你也可以把服務理解為一個對象,但這和一般意義上的對象有些區(qū)別。因為一般的領(lǐng)域?qū)ο蠖际怯袪顟B(tài)和行為的,而領(lǐng)域服務沒有狀態(tài)只有行為。需要強調(diào)的是領(lǐng)域服務是無狀態(tài)的,它存在的意義就是協(xié)調(diào)領(lǐng)域?qū)ο蠊餐瓿赡硞€操作,所有的狀態(tài)還是都保存在相應的領(lǐng)域?qū)ο笾?。我覺得模型(實體)與服務(場景)是對領(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)域服務還有一個很重要的功能就是可以避免領(lǐng)域邏輯泄露到應用層。因為如果沒有領(lǐng)域服務,那么應用層會直接調(diào)用領(lǐng)域?qū)ο笸瓿杀驹撌菍儆陬I(lǐng)域服務該做的操作,這樣一來,領(lǐng)域?qū)涌赡軙岩徊糠诸I(lǐng)域知識泄露到應用層。因為應用層需要了解每個領(lǐng)域?qū)ο蟮臉I(yè)務功能,具有哪些信息,以及它可能會與哪些其他領(lǐng)域?qū)ο蠼换ィ趺唇换サ纫幌盗蓄I(lǐng)域知識。因此,引入領(lǐng)域服務可以有效的防治領(lǐng)域?qū)拥倪壿嬓孤兜綉脤?。對于應用層來說,從可理解的角度來講,通過調(diào)用領(lǐng)域服務提供的簡單易懂但意義明確的接口肯定也要比直接操縱領(lǐng)域?qū)ο笕菀椎亩?。這里似乎也看到了領(lǐng)域服務具有Fa?ade的功能,呵呵。說到領(lǐng)域服務,還需要提一下軟件中一般有三種服務:應用層服務、領(lǐng)域服務、基礎服務。應用層服務獲取輸入(如一個XML請求);發(fā)送消息給領(lǐng)域?qū)臃眨笃鋵崿F(xiàn)轉(zhuǎn)帳的業(yè)務邏輯;領(lǐng)域?qū)臃仗幚沓晒?,則調(diào)用基礎層服務發(fā)送Email通知;領(lǐng)域?qū)臃斋@取源帳號和目標帳號,分別通知源帳號和目標帳號進行扣除金額和增加金額的操作;提供返回結(jié)果給應用層;基礎層服務按照應用層的請求,發(fā)送Email通知;所以,從上面的例子中可以清晰的看出,每種服務的職責;聚合及聚合根(Aggregate,AggregateRoot)聚合,它通過定義對象之間清晰的所屬關(guān)系和邊界來實現(xiàn)領(lǐng)域模型的內(nèi)聚,并避免了錯綜復雜的難以維護的對象關(guān)系網(wǎng)的形成。聚合定義了一組具有內(nèi)聚關(guān)系的相關(guān)對象的集合,我們把聚合看作是一個修改數(shù)據(jù)的單元。聚合有以下一些特點:每個聚合有一個根和一個邊界,邊界定義了一個聚合內(nèi)部有哪些實體或值對象,根是聚合內(nèi)的某個實體;聚合內(nèi)部的對象之間可以相互引用,但是聚合外部如果要訪問聚合內(nèi)部的對象時,必須通過聚合根開始導航,絕對不能繞過聚合根直接訪問聚合內(nèi)的對象,也就是說聚合根是外部可以保持對它的引用的唯一元素;聚合內(nèi)除根以外的其他實體的唯一標識都是本地標識,也就是只要在聚合內(nèi)部保持唯一即可,因為它們總是從屬于這個聚合的;聚合根負責與外部其他對象打交道并維護自己內(nèi)部的業(yè)務規(guī)則;基于聚合的以上概念,我們可以推論出從數(shù)據(jù)庫查詢時的單元也是以聚合為一個單元,也就是說我們不能直接查詢聚合內(nèi)部的某個非根的對象;聚合內(nèi)部的對象可以保持對其他聚合根的引用;刪除一個聚合根時必須同時刪除該聚合內(nèi)的所有相關(guān)對象,因為他們都同屬于一個聚合,是一個完整的概念;關(guān)于如何識別聚合以及聚合根的問題:我覺得我們可以先從業(yè)務的角度深入思考,然后慢慢分析出有哪些對象是:有獨立存在的意義,即它是不依賴于其他對象的存在它才有意義的;可以被獨立訪問的,還是必須通過某個其他對象導航得到的;如何識別聚合?我覺得這個需要從業(yè)務的角度深入分析哪些對象它們的關(guān)系是內(nèi)聚的,即我們會把他們看成是一個整體來考慮的;然后這些對象我們就可以把它們放在一個聚合內(nèi)。所謂關(guān)系是內(nèi)聚的,是指這些對象之間必須保持一個固定規(guī)則,固定規(guī)則是指在數(shù)據(jù)變化時必須保持不變的一致性規(guī)則。當我們在修改一個聚合時,我們必須在事務級別確保整個聚合內(nèi)的所有對象滿足這個固定規(guī)則。作為一條建議,聚合盡量不要太大,否則即便能夠做到在事務級別保持聚合的業(yè)務規(guī)則完整性,也可能會帶來一定的性能問題。有分析報告顯示,通常在大部分領(lǐng)域模型中,有70%的聚合通常只有一個實體,即聚合根,該實體內(nèi)部沒有包含其他實體,只包含一些值對象;另外30%的聚合中,基本上也只包含兩到三個實體。這意味著大部分的聚合都只是一個實體,該實體同時也是聚合根。如何識別聚合根?如果一個聚合只有一個實體,那么這個實體就是聚合根;如果有多個實體,那么我們可以思考聚合內(nèi)哪個對象有獨立存在的意義并且可以和外部直接進行交互。工廠(Factory)DDD中的工廠也是一種體現(xiàn)封裝思想的模式。DDD中引入工廠模式的原因是:有時創(chuàng)建一個領(lǐng)域?qū)ο笫且患容^復雜的事情,不僅僅是簡單的new操作。正如對象封裝了內(nèi)部實現(xiàn)一樣(我們無需知道對象的內(nèi)部實現(xiàn)就可以使用對象的行為),工廠則是用來封裝創(chuàng)建一個復雜對象尤其是聚合時所需的知識,工廠的作用是將創(chuàng)建對象的細節(jié)隱藏起來??蛻魝鬟f給工廠一些簡單的參數(shù),然后工廠可以在內(nèi)部創(chuàng)建出一個復雜的領(lǐng)域?qū)ο笕缓蠓祷亟o客戶。領(lǐng)域模型中其他元素都不適合做這個事情,所以需要引入這個新的模式,工廠。工廠在創(chuàng)建一個復雜的領(lǐng)域?qū)ο髸r,通常會知道該滿足什么業(yè)務規(guī)則(它知道先怎樣實例化一個對象,然后在對這個對象做哪些初始化操作,這些知識就是創(chuàng)建對象的細節(jié)),如果傳遞進來的參數(shù)符合創(chuàng)建對象的業(yè)務規(guī)則,則可以順利創(chuàng)建相應的對象;但是如果由于參數(shù)無效等原因不能創(chuàng)建出期望的對象時,應該拋出一個異常,以確保不會創(chuàng)建出一個錯誤的對象。當然我們也并不總是需要通過工廠來創(chuàng)建對象,事實上大部分情況下領(lǐng)域?qū)ο蟮膭?chuàng)建都不會太復雜,所以我們只需要簡單的使用構(gòu)造函數(shù)創(chuàng)建對象就可以了。隱藏創(chuàng)建對象的好處是顯而易見的,這樣可以不會讓領(lǐng)域?qū)拥臉I(yè)務邏輯泄露到應用層,同時也減輕了應用層的負擔,它只需要簡單的調(diào)用領(lǐng)域工廠創(chuàng)建出期望的對象即可。倉儲(Repository)倉儲被設計出來的目的是基于這個原因:領(lǐng)域模型中的對象自從被創(chuàng)建出來后不會一直留在內(nèi)存中活動的,當它不活動時會被持久化到數(shù)據(jù)庫中,然后當需要的時候我們會重建該對象;重建對象就是根據(jù)數(shù)據(jù)庫中已存儲的對象的狀態(tài)重新創(chuàng)建對象的過程;所以,可見重建對象是一個和數(shù)據(jù)庫打交道的過程。從更廣義的角度來理解,我們經(jīng)常會像集合一樣從某個類似集合的地方根據(jù)某個條件獲取一個或一些對象,往集合中添加對象或移除對象。也就是說,我們需要提供一種機制,可以提供類似集合的接口來幫助我們管理對象。倉儲就是基于這樣的思想被設計出來的;倉儲里面存放的對象一定是聚合,原因是之前提到的領(lǐng)域模型中是以聚合的概念去劃分邊界的;聚合是我們更新對象的一個邊界,事實上我們把整個聚合看成是一個整體概念,要么一起被取出來,要么一起被刪除。我們永遠不會單獨對某個聚合內(nèi)的子對象進行單獨查詢或做更新操作。因此,我們只對聚合設計倉儲。倉儲還有一個重要的特征就是分為倉儲定義部分和倉儲實現(xiàn)部分,在領(lǐng)域模型中我們定義倉儲的接口,而在基礎設施層實現(xiàn)具體的倉儲。這樣做的原因是:由于倉儲背后的實現(xiàn)都是在和數(shù)據(jù)庫打交道,但是我們又不希望客戶(如應用層)把重點放在如何從數(shù)據(jù)庫獲取數(shù)據(jù)的問題上,因為這樣做會導致客戶(應用層)代碼很混亂,很可能會因此而忽略了領(lǐng)域模型的存在。所以我們需要提供一個簡單明了的接口,供客戶使用,確保客戶能以最簡單的方式獲取領(lǐng)域?qū)ο?,從而可以讓它專心的不會被什么?shù)據(jù)訪問代碼打擾的情況下協(xié)調(diào)領(lǐng)域?qū)ο笸瓿蓸I(yè)務邏輯。這種通過接口來隔離封裝變化的做法其實很常見。由于客戶面對的是抽象的接口并不是具體的實現(xiàn),所以我們可以隨時替換倉儲的真實實現(xiàn),這很有助于我們做單元測試。盡管倉儲可以像集合一樣在內(nèi)存中管理對象,但是倉儲一般不負責事務處理。一般事務處理會交給一個叫“工作單元(UnitOfWork)”的東西。關(guān)于工作單元的詳細信息我在下面的討論中會講到。另外,倉儲在設計查詢接口時,可能還會用到規(guī)格模式(SpecificationPattern),我見過的最厲害的規(guī)格模式應該就是LINQ以及DLINQ查詢了。一般我們會根據(jù)項目中查詢的靈活度要求來選擇適合的倉儲查詢接口設計。通常情況下只需要定義簡單明了的具有固定查詢參數(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)域概念的涵義以及包含的主要信息;分析主要的軟件應用程序功能,識別出主要的應用層的類;這樣有助于及早發(fā)現(xiàn)哪些是應用層的職責,哪些是領(lǐng)域?qū)拥穆氊?;進一步分析領(lǐng)域模型,識別出哪些是實體,哪些是值對象,哪些是領(lǐng)域服務;分析關(guān)聯(lián),通過對業(yè)務的更深入分析以及各種軟件設計原則及性能方面的權(quán)衡,明確關(guān)聯(lián)的方向或者去掉一些不需要的關(guān)聯(lián);找出聚合邊界及聚合根,這是一件很有難度的事情;因為你在分析的過程中往往會碰到很多模棱兩可的難以清晰判斷的選擇問題,所以,需要我們平時一些分析經(jīng)驗的積累才能找出正確的聚合根;為聚合根配備倉儲,一般情況下是為一個聚合分配一個倉儲,此時只要設計好倉儲的接口即可;走查場景,確定我們設計的領(lǐng)域模型能夠有效地解決業(yè)務需求;考慮如何創(chuàng)建領(lǐng)域?qū)嶓w或值對象,是通過工廠還是直接通過構(gòu)造函數(shù);停下來重構(gòu)模型。尋找模型中覺得有些疑問或者是蹩腳的地方,比如思考一些對象應該通過關(guān)聯(lián)導航得到還是應該從倉儲獲???聚合設計的是否正確?考慮模型的性能怎樣,等等;領(lǐng)域建模是一個不斷重構(gòu),持續(xù)完善模型的過程,大家會在討論中將變化的部分反映到模型中,從而是模型不斷細化并朝正確的方向走。領(lǐng)域建模是領(lǐng)域?qū)<摇⒃O計人員、開發(fā)人員之間溝通交流的過程,是大家工作和思考問題的基礎。搜索公眾號頂級架構(gòu)師后臺回復“手冊”,獲取一份驚喜禮包。從經(jīng)典的領(lǐng)域驅(qū)動設計分層架構(gòu)中可以看出,領(lǐng)域?qū)拥纳蠈邮菓脤?,下層是基礎設施層。那么領(lǐng)域?qū)邮侨绾闻c其它層交互的呢?對于會影響領(lǐng)域?qū)又蓄I(lǐng)域?qū)ο鬆顟B(tài)的應用層功能一般應用層會先啟動一個工作單元,然后:對于修改領(lǐng)域?qū)ο蟮那闆r,通過倉儲獲取領(lǐng)域?qū)ο?,調(diào)用領(lǐng)域?qū)ο蟮南嚓P(guān)業(yè)務方法以完成業(yè)務邏輯處理;對于新增領(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ū)ο蟮奈ㄒ粯俗R給倉儲通知其移除該唯一標識對應領(lǐng)域?qū)ο?;如果一個業(yè)務邏輯涉及到多個領(lǐng)域?qū)ο?,則調(diào)用領(lǐng)域?qū)又械南嚓P(guān)領(lǐng)域服務完成操作;注意,以上所說的所有領(lǐng)域?qū)ο蠖际侵痪酆细?,另外在應用層需要獲取倉儲接口以及領(lǐng)域服務接口時,都可以通過IOC容器獲取。最后通知工作單元提交事務從而將所有相關(guān)的領(lǐng)域?qū)ο蟮臓顟B(tài)以事務的方式持久化到數(shù)據(jù)庫;關(guān)于UnitofWork(工作單元)的幾種實現(xiàn)方法基于快照的實現(xiàn),即領(lǐng)域?qū)ο蟊蝗〕鰜砗螅瑫缺4嬉粋€備份的對象,然后當在做持久化操作時,將最新的對象的狀態(tài)和備份的對象的狀態(tài)進行比較,如果不相同,則認為有做過修改,然后進行持久化;這種設計的好處是對象不用告訴工作單元自己的狀態(tài)修改了,而缺點也是顯而易見的,那就是性能可能會低,備份對象以及比較對象的狀態(tài)是否有修改的過程在當對象本身很復雜的時候,往往是一個比較耗時的步驟,而且要真正實現(xiàn)對象的深拷貝以及判斷屬性是否修改還是比較困難的;不基于快照,而是倉儲的相關(guān)更新或新增或刪除接口被調(diào)用時,倉儲通知工作單元某個對象被新增了或更新了或刪除了。這樣工作單元在做數(shù)據(jù)持久化時也同樣可以知道需要持久化哪些對象了;這種方法理論上不需要ORM框架的支持,對領(lǐng)域模型也沒有任何傾入性,同時也很好的支持了工作單元的模式。對于不想用高級ORM框架的朋友來說,這種方法挺好;不基于快照,也不用倉儲告訴工作單元數(shù)據(jù)更改了。而是采用AOP的思想,采用透明代理的方式進行一個攔截。在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)域模型的傾入性最強。對于不會影響領(lǐng)域?qū)又蓄I(lǐng)域?qū)ο鬆顟B(tài)的查詢功能可以直接通過倉儲查詢出所需要的數(shù)據(jù)。但一般領(lǐng)域?qū)又械膫}儲提供的查詢功能也許不能滿足界面顯示的需要,則可能需要多次調(diào)用不同的倉儲才能獲取所需要顯示的數(shù)據(jù);其實針對這種查詢的情況,我在后面會講到可以直接通過CQRS的架構(gòu)來實現(xiàn)。即對于查詢,我們可以在應用層不調(diào)用領(lǐng)域?qū)拥娜魏螙|西,而是直接通過某個其他的用另外的技術(shù)架構(gòu)實現(xiàn)的查詢引擎來完成查詢,比如直接通過構(gòu)造參數(shù)化SQL的方式從數(shù)據(jù)庫一個表或多個表中查詢出任何想要顯示的數(shù)據(jù)。這樣不僅性能高,也可以減輕領(lǐng)域?qū)拥呢摀?。領(lǐng)域模型不太適合為應用層提供各種查詢服務,因為往往界面上要顯示的數(shù)據(jù)是很多對象的組合信息,是一種非對象概念的信息,就像報表;對象將需求用類一個個隔開,就像用儲物箱把東西一個個封裝起來一樣,需求變了,分幾種情況,最嚴重的是大變,那么每個儲物箱都要打開改,這種方法就不見得有好處;但是這種情況發(fā)生概率比較小,大部分需求變化都是局限在一兩個儲物箱中,那么我們只要打開這兩個儲物箱修改就可以,不會影響其他儲物柜了。而面向過程是把所有東西都放在一個大儲物箱中,修改某個部分以后,會引起其他部分不穩(wěn)定,一個BUG修復,引發(fā)新的無數(shù)BUG,最后程序員陷入焦頭爛額,如日本東京電力公司員工處理核危機一樣,心力交瘁啊。所以,我們不能粗粒度看需求變,認為需求變了,就是大范圍變,萬事萬物都有邊界,老子說,無欲觀其繳,什么事物都要觀察其邊界,雖然需求可以用“需求”這個名詞表達,談到需求變了,不都意味著最大邊界范圍的變化,這樣看問題容易走極端。其實就是就地畫圈圈——邊界。我們小時候?qū)懽魑姆掷先我彩峭瑯拥览恚髯月氊熋鞔_,劃分邊界明確,通過過渡句實現(xiàn)承上啟下——接口。為什么組織需要分不同部門,同樣是邊界思維。畫圈圈容易,但如何畫才難,所以OO中思維非常重要。需求變化所引起的變化是有邊界,若果變化的邊界等于整個領(lǐng)域,那么已經(jīng)是完全不同的項目了。要掌握邊界,是需要大量的領(lǐng)域知識的。否則,走進銀行連業(yè)務職責都分不清的,如何畫圈圈呢?面向過程是無邊界一詞的(就算有也只是最大的邊界),它沒有要求各自獨立,它可以橫跨邊界進行調(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ì)就是邊界劃分,封裝,不但對需求變化能夠量化,縮小影響面;因為邊界劃分也會限制出錯的影響范圍,所以OO對軟件后期BUG等出錯也有好處。軟件世界永遠都有BUG,BUG是清除不干凈的,就像人類世界永遠都存在不完美和陰暗面,問題關(guān)鍵是:上帝用空間和時間的邊界把人類世界痛苦災難等不完美局限在一個范圍內(nèi);而軟件世界如果你不采取OO等方法進行邊界劃分的話,一旦出錯,追查起來情況會有多糟呢?軟件世界其實類似人類現(xiàn)實世界,有時出問題了,探究原因一看,原來是兩個看上去毫無聯(lián)系的因素導致的,古人只好經(jīng)常求神拜佛,我們程序員在自己的軟件上線運行時,大概心里也在求神拜佛別出大紕漏,如果我們的軟件采取OO封裝,我們就會坦然些,肯定會出錯,但是我們已經(jīng)預先劃定好邊界,所以,不會產(chǎn)生嚴重后果,甚至也不會出現(xiàn)難以追查的魔鬼BUG。上面只是涉及到DDD中最基本的內(nèi)容,DDD中還有很多其他重要的內(nèi)容在上面沒有提到,如:模型上下文、上下文映射、上下文共享;如何將分析模式和設計模式運用到DDD中;一些關(guān)于柔性設計的技巧;如果保持模型完整性,以及持續(xù)集成方面的知識;如何精煉模型,識別核心模型以及通用子領(lǐng)域;這些主題都很重要,因為篇幅有限以及我目前掌握的知識也有限,并且為了突出這篇文章的重點,所以不對他們做詳細介紹了,大家有興趣的可以自己閱讀一下。CQRS架構(gòu)核心思想是將應用程序的查詢部分和命令部分完全分離,這兩部分可以用完全不同的模型和技術(shù)去實現(xiàn)。比如命令部分可以通過領(lǐng)域驅(qū)動設計來實現(xiàn);查詢部分可以直接用最快的非面向?qū)ο蟮姆绞饺崿F(xiàn),比如用SQL。這樣的思想有很多好處:實現(xiàn)命令部分的領(lǐng)域模型不用經(jīng)常為了領(lǐng)域?qū)ο罂赡軙蝗绾尾樵兌鲆恍┱壑刑幚?;由于命令和查詢是完全分離的,所以這兩部分可以用不同的技術(shù)架構(gòu)實現(xiàn),包括數(shù)據(jù)庫設計都可以分開設計,每一部分可以充分發(fā)揮其長處;高性能,命令端因為沒有返回值,可以像消息隊列一樣接受命令,放在隊列中,慢慢處理;處理完后,可以通過異步的方式通知查詢端,這樣查詢端可以做數(shù)據(jù)同步的處理;EventSourcing(事件溯源)對于DDD中的聚合,不保存聚合的當前狀態(tài),而是保存對象上所發(fā)生的每個事件。當要重建一個聚合對象時,可以通過回溯這些事件(即讓這些事件重新發(fā)生)來讓對象恢復到某個特定的狀態(tài);因為有時一個聚合可能會發(fā)生很多事件,所以如果每次要在重建對象時都從頭回溯事件,會導致性能低下,所以我們會在一定時候為聚合創(chuàng)建一個快照。這樣,我們就可以基于某個快照開始創(chuàng)建聚合對象了。DCI架構(gòu)DCI架構(gòu)強調(diào),軟件應該真實的模擬現(xiàn)實生活中對象的交互方式,代碼應該準確樸實的反映用戶的心智模型。在DCI中有:數(shù)據(jù)模型、角色模型、以及上下文這三個概念。數(shù)據(jù)模型表示程序的結(jié)構(gòu),目前我們所理解的DDD中的領(lǐng)域模型可以很好的表示數(shù)據(jù)模型;角色模型表示數(shù)據(jù)如何交互,一個角色定義了某個“身份”所具有的交互行為;上下文對應業(yè)務場景,用于實現(xiàn)業(yè)務用例,注意是業(yè)務用例而不是系統(tǒng)用例,業(yè)務用例只與業(yè)務相關(guān);軟件運行時,根據(jù)用戶的操作,系統(tǒng)創(chuàng)建相應的場景,并把相關(guān)的數(shù)據(jù)對象作為場景參與者傳遞給場景,然后場景知道該為每個對象賦予什么角色,當對象被賦予某個角色后就真正成為有交互能力的對象,然后與其他對象進行交互;這個過程與現(xiàn)實生活中我們所理解的對象是一致的;DCI的這種思想與DDD中的領(lǐng)域服務所做的事情是一樣的,但實現(xiàn)的角度有些不同。DDD中的領(lǐng)域服務被創(chuàng)建的出發(fā)點是當一些職責不太適合放在任何一個領(lǐng)域?qū)ο笊蠒r,這個職責往往對應領(lǐng)域中的某個活動或轉(zhuǎn)換過程,此時我們應該考慮將其放在一個服務中。比如資金轉(zhuǎn)帳的例子,我們應該提供一個資金轉(zhuǎn)帳的服務,用來對應領(lǐng)域中的資金轉(zhuǎn)帳這個領(lǐng)域概念。但是領(lǐng)域服務內(nèi)部做的事情是協(xié)調(diào)多個領(lǐng)域?qū)ο笸瓿梢患虑?。因此,在DDD中的領(lǐng)域服務
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年度新型電動出租車購置合同范本4篇
- 2025年度協(xié)議離婚房產(chǎn)分割合同范本3篇
- 2024起重機研發(fā)、制造與銷售合作框架合同3篇
- 2024版建筑腳手架施工安全合作合同書版B版
- 2024藥品研發(fā)生產(chǎn)項目廉潔合作合同范本3篇
- 2024智能化倉儲管理系統(tǒng)采購與升級合同2篇
- 2025年度知識產(chǎn)權(quán)出借與咨詢服務合同4篇
- 2025年度知識產(chǎn)權(quán)評估居間合同上訴狀4篇
- 2024離婚雙方關(guān)于調(diào)解程序的協(xié)議
- 2024版毛竹購銷合同模板
- 春節(jié)行車安全常識普及
- 電機維護保養(yǎng)專題培訓課件
- 汽車租賃行業(yè)利潤分析
- 春節(jié)拜年的由來習俗來歷故事
- 2021火災高危單位消防安全評估導則
- 佛山市服務業(yè)發(fā)展五年規(guī)劃(2021-2025年)
- 房屋拆除工程監(jiān)理規(guī)劃
- 醫(yī)院保安服務方案(技術(shù)方案)
- 高效能人士的七個習慣:實踐應用課程:高級版
- 小數(shù)加減法計算題100道
- 通信電子線路(哈爾濱工程大學)智慧樹知到課后章節(jié)答案2023年下哈爾濱工程大學
評論
0/150
提交評論