色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

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

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

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

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

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ū)動(dòng)設(shè)計(jì)),簡(jiǎn)稱Evans DDD。領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)分為兩個(gè)階段:

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

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

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

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

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

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

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

領(lǐng)域模型貫穿軟件分析、設(shè)計(jì),以及開發(fā)的整個(gè)過程;領(lǐng)域?qū)<摇⒃O(shè)計(jì)人員、開發(fā)人員通過領(lǐng)域模型進(jìn)行交流,彼此共享知識(shí)與信息;因?yàn)榇蠹颐嫦虻亩际峭粋€(gè)模型,所以可以防止需求走樣,可以讓軟件設(shè)計(jì)開發(fā)人員做出來的軟件真正滿足需求;

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

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

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

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

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

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

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

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

“用戶需求”不能等同于“用戶”,捕捉“用戶心中的模型”也不能等同于“以用戶為核心設(shè)計(jì)領(lǐng)域模型”。《老子》書中有個(gè)觀點(diǎn):有之以為利,無之以為用。在這里,有之利,即建立領(lǐng)域模型;無之用,即包容用戶需求。舉些例子,一個(gè)杯子要裝滿一杯水,我們?cè)谥谱鞅訒r(shí),制作的是空杯子,即要把水倒出來,之后才能裝下水;再比如,一座房子要住人,我們?cè)诮ㄔ旆孔訒r(shí),建造的房子是空的,唯有空的才能容納人的居住。因此,建立領(lǐng)域模型時(shí)也要將用戶置于模型之外,這樣才能包容用戶的需求。

所以,我的理解是:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

應(yīng)用層

很薄的一層,定義軟件要完成的所有任務(wù)。對(duì)外為展現(xiàn)層提供各種應(yīng)用功能(包括查詢或命令),對(duì)內(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ū)訉?shí)現(xiàn)持久化機(jī)制;總之,基礎(chǔ)設(shè)施層可以通過架構(gòu)和框架來支持其他層的技術(shù)需求;

所有模式的總攬圖

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

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

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

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

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

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

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

實(shí)體(Entity)

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

值對(duì)象(Value Object)

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

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

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

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

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

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

獲取輸入(如一個(gè)XML請(qǐng)求);

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

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

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

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

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

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

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

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

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

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

聚合有以下一些特點(diǎn):

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

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

聚合內(nèi)除根以外的其他實(shí)體的唯一標(biāo)識(shí)都是本地標(biāo)識(shí),也就是只要在聚合內(nèi)部保持唯一即可,因?yàn)樗鼈兛偸菑膶儆谶@個(gè)聚合的;

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

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

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

刪除一個(gè)聚合根時(shí)必須同時(shí)刪除該聚合內(nèi)的所有相關(guān)對(duì)象,因?yàn)樗麄兌纪瑢儆谝粋€(gè)聚合,是一個(gè)完整的概念;

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

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

有獨(dú)立存在的意義,即它是不依賴于其他對(duì)象的存在它才有意義的;

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

如何識(shí)別聚合?

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

如何識(shí)別聚合根?

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

工廠(Factory)

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

倉(cāng)儲(chǔ)(Repository)

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

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

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

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

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

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

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

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

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

找出聚合邊界及聚合根,這是一件很有難度的事情;因?yàn)槟阍诜治龅倪^程中往往會(huì)碰到很多模棱兩可的難以清晰判斷的選擇問題,所以,需要我們平時(shí)一些分析經(jīng)驗(yàn)的積累才能找出正確的聚合根;

為聚合根配備倉(cāng)儲(chǔ),一般情況下是為一個(gè)聚合分配一個(gè)倉(cāng)儲(chǔ),此時(shí)只要設(shè)計(jì)好倉(cāng)儲(chǔ)的接口即可;

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

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

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

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

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

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

一般應(yīng)用層會(huì)先啟動(dòng)一個(gè)工作單元,然后:

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

對(duì)于新增領(lǐng)域?qū)ο蟮那闆r,通過構(gòu)造函數(shù)或工廠創(chuàng)建出領(lǐng)域?qū)ο螅绻枰€可以繼續(xù)對(duì)該新創(chuàng)建的領(lǐng)域?qū)ο笞鲆恍┎僮?,然后把該新?chuàng)建的領(lǐng)域?qū)ο筇砑拥絺}(cāng)儲(chǔ)中;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

面向?qū)ο蟮膶?shí)質(zhì)就是邊界劃分,封裝,不但對(duì)需求變化能夠量化,縮小影響面;因?yàn)檫吔鐒澐忠矔?huì)限制出錯(cuò)的影響范圍,所以O(shè)O對(duì)軟件后期BUG等出錯(cuò)也有好處。

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

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

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

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

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

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

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

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

這些主題都很重要,因?yàn)槠邢抟约拔夷壳罢莆盏闹R(shí)也有限,并且為了突出這篇文章的重點(diǎn),所以不對(duì)他們做詳細(xì)介紹了,大家有興趣的可以自己閱讀一下。

CQRS架構(gòu)

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

實(shí)現(xiàn)命令部分的領(lǐng)域模型不用經(jīng)常為了領(lǐng)域?qū)ο罂赡軙?huì)被如何查詢而做一些折中處理;

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

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

Event Sourcing(事件溯源)

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

DCI架構(gòu)

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

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

四色原型分析模式

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

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

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

表示參與某個(gè)活動(dòng)的人或物,地點(diǎn)則是活動(dòng)的發(fā)生地。使用綠色表示。簡(jiǎn)寫為PPT。

描述原型(Description Archetype)

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

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

角色原型(Role Archetype)

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

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

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





審核編輯:劉清

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

    關(guān)注

    0

    文章

    188

    瀏覽量

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

    關(guān)注

    180

    文章

    7608

    瀏覽量

    137200
  • 驅(qū)動(dòng)設(shè)計(jì)

    關(guān)注

    1

    文章

    111

    瀏覽量

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

    關(guān)注

    0

    文章

    23

    瀏覽量

    2935

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

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

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    文理解BOOST電路的升壓原理

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

    文理解示波器帶寬

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

    文理解多模態(tài)大語言模型——上

    利于中文讀者理解的目標(biāo),做了刪減、重構(gòu)和意譯,并替換了多張不適合中文讀者的示意圖。 原文地址:https://magazine.sebastianraschka.com/p
    的頭像 發(fā)表于 12-02 18:29 ?384次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文理解</b>多模態(tài)大語言模型——上

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

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

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

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

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

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

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

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

    文理解python模塊的緩存

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

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

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

    文理解PID算法

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

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

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

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

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

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

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

    DDD學(xué)習(xí)與感悟——向屎山?jīng)_鋒

    軟件系統(tǒng)是通過軟件開發(fā)來解決某個(gè)業(yè)務(wù)領(lǐng)域或問題單元而產(chǎn)生的個(gè)交付物。而通過軟件設(shè)計(jì)可以幫助我們開發(fā)出更加健壯的軟件系統(tǒng)。因此,軟件設(shè)計(jì)是從業(yè)務(wù)領(lǐng)域到軟件開發(fā)之間的橋梁。而
    的頭像 發(fā)表于 09-24 13:31 ?246次閱讀
    <b class='flag-5'>DDD</b>學(xué)習(xí)與感悟——向屎山?jīng)_鋒

    文理解多模態(tài)大語言模型——下

    /understanding-multimodal-llms ? 《文理解多模態(tài)大語言模型 - 上》介紹了什么是多模態(tài)大語言模型,以及構(gòu)建多模態(tài) LLM 有兩種主要方式之:統(tǒng)嵌入
    的頭像 發(fā)表于 12-03 15:18 ?159次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文理解</b>多模態(tài)大語言模型——下
    主站蜘蛛池模板: 公交车轮C关老师| 国产久青青青青在线观看| 亚洲精品蜜桃AV久久久| 亚洲精品电影久久久影院| 1000部做羞羞事禁片免费视频网站| 中文字幕在线观看| Zoofilivideo人馿交| 国产自产视频在线观看香蕉| 久久中文字幕人妻AV熟女| 人妖和美女玩| 亚洲三区视频| av在线不卡中文网| 国产精品无码久久久久不卡| 色青青草原桃花久久综合| 一级黄色香蕉视频| 国产成人精品免费视频大 | 老司机福利视频一区在线播放| 视频在线观看高清免费看| 友田真希息与子中文字幕| 粉嫩自拍 偷拍 亚洲| 两个奶被男人揉了一个晚上| 76人遣返航班上71人呈阳性| 护士12p| 涩涩游戏盒| 国产人妻人伦精品1国产| 日本一卡2卡3卡四卡精品网站| 伊人久久大香| 男人被绑着强行摸j| 亚洲男女羞羞无遮挡久久丫| 久久er国产免费精品| 午夜伦理电影在线观免费| 99综合之综合久久伊人| 日本性hd| 国内精品自产拍在线少密芽| 战狼4在线观看完免费完整版 | 国产欧美一区二区三区久久| 亚洲色图在线视频| 狠狠色狠狠色狠狠五月ady| 伊人久久综合谁合综合久久| 欧美高清18| 最新亚洲一区二区三区四区|