九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)

2004年,當(dāng)Eric Evans的那本《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)——軟件核心復(fù)雜性應(yīng)對(duì)之道》(后文簡(jiǎn)稱(chēng)《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》)出版時(shí),我還在念高中,接觸到領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)已經(jīng)是8年后的事情了。那時(shí),我正打算在軟件開(kāi)發(fā)之路上更進(jìn)一步,經(jīng)同事介紹,我開(kāi)始接觸DDD。

 

我想,多數(shù)有經(jīng)驗(yàn)的程序開(kāi)發(fā)者都應(yīng)該聽(tīng)說(shuō)過(guò)DDD,并且嘗試過(guò)將其應(yīng)用在自己的項(xiàng)目中。不知你是否遇到過(guò)這樣的場(chǎng)景:你創(chuàng)建了一個(gè)資源庫(kù)(Repository),但一段時(shí)間之后發(fā)現(xiàn)這個(gè)資源庫(kù)和傳統(tǒng)的DAO越來(lái)越像了,你開(kāi)始反思自己的實(shí)現(xiàn)方式是正確的嗎?或者,你創(chuàng)建了一個(gè)聚合,然后發(fā)現(xiàn)這個(gè)聚合是如此的龐大,它為什么引用了如此多的對(duì)象,難道又是我做錯(cuò)了嗎?

 

其實(shí)你并不孤單,我相信多數(shù)同仁都曾遇到過(guò)相似的問(wèn)題。前不久,我一個(gè)同事給我展示了他在2007年買(mǎi)的那本已經(jīng)被他韋編三絕過(guò)的《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》,他告訴我,讀過(guò)好幾遍后,他依然不知道如何將DDD付諸實(shí)踐。Eric那本書(shū)固然是好,無(wú)可否認(rèn),但是我們程序員總希望看到一些實(shí)際的例子能夠切實(shí)將DDD落地以指導(dǎo)我們的日常開(kāi)發(fā)。

 

于是,在Eric的書(shū)出版將近10年之后,我們有了Vaughn Vernon《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》,作為該書(shū)的譯者,我有幸通讀了本書(shū),受益匪淺,得到的結(jié)論是:好的軟件就應(yīng)該是DDD的。

 

 

就像在微電子領(lǐng)域有知識(shí)產(chǎn)權(quán)核(Intellectual Property)一樣,DDD將一個(gè)軟件系統(tǒng)的核心業(yè)務(wù)功能集中在一個(gè)核心域里面,其中包含了實(shí)體、值對(duì)象、領(lǐng)域服務(wù)、資源庫(kù)和聚合等概念。在此基礎(chǔ)上,DDD提出了一套完整的支撐這樣的核心領(lǐng)域的基礎(chǔ)設(shè)施。此時(shí),DDD已經(jīng)不再是“面向?qū)ο筮M(jìn)階”那么簡(jiǎn)單了,而是演變成了一個(gè)系統(tǒng)工程。

 

所謂領(lǐng)域,即是一個(gè)組織的業(yè)務(wù)開(kāi)展方式,業(yè)務(wù)價(jià)值便體現(xiàn)在其中。長(zhǎng)久以來(lái),我們程序員都是很好的技術(shù)型思考者,我們總是擅長(zhǎng)從技術(shù)的角度來(lái)解決項(xiàng)目問(wèn)題。但是,一個(gè)軟件系統(tǒng)是否真正可用是通過(guò)它所提供的業(yè)務(wù)價(jià)值體現(xiàn)出來(lái)的。因此,與其每天鉆在那些永遠(yuǎn)也學(xué)不完的技術(shù)中,何不將我們的關(guān)注點(diǎn)向軟件系統(tǒng)所提供的業(yè)務(wù)價(jià)值方向思考思考,這也正是DDD所試圖解決的問(wèn)題。

 

在DDD中,代碼就是設(shè)計(jì)本身,你不再需要那些繁文縟節(jié)的并且永遠(yuǎn)也無(wú)法得到實(shí)時(shí)更新的設(shè)計(jì)文檔。編碼者與領(lǐng)域?qū)<以僖膊恍枰g才能理解對(duì)方所表達(dá)的意思。

 

DDD有戰(zhàn)略設(shè)計(jì)和戰(zhàn)術(shù)設(shè)計(jì)之分。戰(zhàn)略設(shè)計(jì)主要從高層“俯視”我們的軟件系統(tǒng),幫助我們精準(zhǔn)地劃分領(lǐng)域以及處理各個(gè)領(lǐng)域之間的關(guān)系;而戰(zhàn)術(shù)設(shè)計(jì)則從技術(shù)實(shí)現(xiàn)的層面教會(huì)我們?nèi)绾尉唧w地實(shí)施DDD。

 

DDD之戰(zhàn)略設(shè)計(jì)

需要指出的是,DDD絕非一套單純的技術(shù)工具集,但是我所看到的很多程序員卻的確是這么認(rèn)為的,并且也是懷揣著這樣的想法來(lái)使用DDD的。過(guò)于拘泥于技術(shù)上的實(shí)現(xiàn)將導(dǎo)致DDD-Lite。簡(jiǎn)單來(lái)講,DDD-Lite將導(dǎo)致劣質(zhì)的領(lǐng)域?qū)ο?,因?yàn)槲覀兒雎粤薉DD戰(zhàn)略建模所帶來(lái)的好處。

 

DDD的戰(zhàn)略設(shè)計(jì)主要包括領(lǐng)域/子域、通用語(yǔ)言、限界上下文和架構(gòu)風(fēng)格等概念。

 

領(lǐng)域和子域(Domain/Subdomain)

既然是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),那么我們主要的關(guān)注點(diǎn)理所當(dāng)然應(yīng)該放在如何設(shè)計(jì)領(lǐng)域模型上,以及對(duì)領(lǐng)域模型的劃分。

 

領(lǐng)域并不是多么高深的概念,比如,一個(gè)保險(xiǎn)公司的領(lǐng)域中包含了保險(xiǎn)單、理賠和再保險(xiǎn)等概念;一個(gè)電商網(wǎng)站的領(lǐng)域包含了產(chǎn)品名錄、訂單、發(fā)票、庫(kù)存和物流的概念。這里,我主要講講對(duì)領(lǐng)域的劃分,即將一個(gè)大的領(lǐng)域劃分成若干個(gè)子域。

 

在日常開(kāi)發(fā)中,我們通常會(huì)將一個(gè)大型的軟件系統(tǒng)拆分成若干個(gè)子系統(tǒng)。這種劃分有可能是基于架構(gòu)方面的考慮,也有可能是基于基礎(chǔ)設(shè)施的。但是在DDD中,我們對(duì)系統(tǒng)的劃分是基于領(lǐng)域的,也即是基于業(yè)務(wù)的。

 

于是,問(wèn)題也來(lái)了:首先,哪些概念應(yīng)該建模在哪些子系統(tǒng)里面?我們可能會(huì)發(fā)現(xiàn)一個(gè)領(lǐng)域概念建模在子系統(tǒng)A中是可以的,而建模在子系統(tǒng)B中似乎也合乎情理。第二個(gè)問(wèn)題是,各個(gè)子系統(tǒng)之間的應(yīng)該如何集成?有人可能會(huì)說(shuō),這不簡(jiǎn)單得就像客戶端調(diào)用服務(wù)端那么簡(jiǎn)單嗎?問(wèn)題在于,兩個(gè)系統(tǒng)之間的集成涉及到基礎(chǔ)設(shè)施和不同領(lǐng)域概念在兩個(gè)系統(tǒng)之間的翻譯,稍不注意,這些概念就會(huì)對(duì)我們精心創(chuàng)建好的領(lǐng)域模型造成污染。

 

如何解決?答案是:限界上下文和上下文映射圖。

 

限界上下文(Bounded Context)

在一個(gè)領(lǐng)域/子域中,我們會(huì)創(chuàng)建一個(gè)概念上的領(lǐng)域邊界,在這個(gè)邊界中,任何領(lǐng)域?qū)ο蠖贾槐硎咎囟ㄓ谠撨吔鐑?nèi)部的確切含義。這樣邊界便稱(chēng)為限界上下文。限界上下文和領(lǐng)域具有一對(duì)一的關(guān)系。

 

舉個(gè)例子,同樣是一本書(shū),在出版階段和出售階段所表達(dá)的概念是不同的,出版階段我們主要關(guān)注的是出版日期,字?jǐn)?shù),出版社和印刷廠等概念,而在出售階段我們則主要關(guān)心價(jià)格,物流和發(fā)票等概念。我們應(yīng)該怎么辦呢,將所有這些概念放在單個(gè)Book對(duì)象中嗎?這不是DDD的做法,DDD有限界上下文將這兩個(gè)不同的概念區(qū)分開(kāi)來(lái)。

 

從物理上講,一個(gè)限界上下文最終可以是一個(gè)DLL(.NET)文件或者JAR(Java)文件,甚至可以是一個(gè)命名空間(比如Java的package)中的所有對(duì)象。但是,技術(shù)本身并不應(yīng)該用來(lái)界分限界上下文。

 

將一個(gè)限界上下文中的所有概念,包括名詞、動(dòng)詞和形容詞全部集中在一起,我們便為該限界上下文創(chuàng)建了一套通用語(yǔ)言。通用語(yǔ)言是一個(gè)團(tuán)隊(duì)所有成員交流時(shí)所使用的語(yǔ)言,業(yè)務(wù)分析人員、編碼人員和測(cè)試人員都應(yīng)該直接通過(guò)通用語(yǔ)言進(jìn)行交流。

 

對(duì)于上文中提到的各個(gè)子域之間的集成問(wèn)題,其實(shí)也是限界上下文之間的集成問(wèn)題。在集成時(shí),我們主要關(guān)心的是領(lǐng)域模型和集成手段之間的關(guān)系。比如需要與一個(gè)REST資源集成,你需要提供基礎(chǔ)設(shè)施(比如Spring 中的RestTemplate),但是這些設(shè)施并不是你核心領(lǐng)域模型的一部分,你應(yīng)該怎么辦呢?答案是防腐層,該層負(fù)責(zé)與外部服務(wù)提供方打交道,還負(fù)責(zé)將外部概念翻譯成自己的核心領(lǐng)域能夠理解的概念。當(dāng)然,防腐層只是限界上下文之間眾多集成方式的一種,另外還有共享內(nèi)核、開(kāi)放主機(jī)服務(wù)等,具體細(xì)節(jié)請(qǐng)參考《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》原書(shū)。限界上下文之間的集成關(guān)系也可以理解為是領(lǐng)域概念在不同上下文之間的映射關(guān)系,因此,限界上下文之間的集成也稱(chēng)為上下文映射圖。

 

架構(gòu)風(fēng)格(Architecture)

DDD并不要求采用特定的架構(gòu)風(fēng)格,因?yàn)樗菍?duì)架構(gòu)中立的。你可以采用傳統(tǒng)的三層式架構(gòu),也可以采用REST架構(gòu)和事件驅(qū)動(dòng)架構(gòu)等。但是在《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》中,作者比較推崇事件驅(qū)動(dòng)架構(gòu)和六邊形(Hexagonal)架構(gòu)。

 

當(dāng)下,面向接口編程和依賴(lài)注入原則已經(jīng)在顛覆著傳統(tǒng)的分層架構(gòu),如果再進(jìn)一步,我們便得到了六邊形架構(gòu),也稱(chēng)為端口和適配器(Ports and Adapters)。在六邊形架構(gòu)中,已經(jīng)不存在分層的概念,所有組件都是平等的。這主要得益于軟件抽象的好處,即各個(gè)組件的之間的交互完全通過(guò)接口完成,而不是具體的實(shí)現(xiàn)細(xì)節(jié)。正如Robert C. Martin所說(shuō):

 

抽象不應(yīng)該依賴(lài)于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴(lài)于抽象。

 

采用六邊形架構(gòu)的系統(tǒng)中存在著很多端口和適配器的組合。端口表示的是一個(gè)軟件系統(tǒng)的輸入和輸出,而適配器則是對(duì)每一個(gè)端口的訪問(wèn)方式。比如,在一個(gè)Web應(yīng)用程序中,HTTP協(xié)議可以作為一個(gè)端口,它向用戶提供HTML頁(yè)面并且接受用戶的表單提交;而Servlet(對(duì)于Java而言)或者Spring中的Controller則是相對(duì)應(yīng)于HTTP協(xié)議的適配器。再比如,要對(duì)數(shù)據(jù)進(jìn)行持久化,此時(shí)的數(shù)據(jù)庫(kù)系統(tǒng)則可看成是一個(gè)端口,而訪問(wèn)數(shù)據(jù)庫(kù)的Driver則是相應(yīng)于數(shù)據(jù)庫(kù)的適配器。如果要為系統(tǒng)增加新的訪問(wèn)方式,你只需要為該訪問(wèn)方式添加一個(gè)相應(yīng)的端口和適配器即可。

 

那么,我們的領(lǐng)域模型又如何與端口和適配器進(jìn)行交互呢?

 

上文已經(jīng)提到,軟件系統(tǒng)的真正價(jià)值在于提供業(yè)務(wù)功能,我們會(huì)將所有的業(yè)務(wù)功能分解為若干個(gè)業(yè)務(wù)用例,每一次業(yè)務(wù)用例都表示對(duì)軟件系統(tǒng)的一次原子操作。所以首先,軟件系統(tǒng)中應(yīng)該存在這樣的組件,他們的作用即以業(yè)務(wù)用例為單位向外界暴露該系統(tǒng)的業(yè)務(wù)功能。在DDD中,這樣的組件稱(chēng)為應(yīng)用層(Application Layer)。

 

在有了應(yīng)用層之后,軟件系統(tǒng)和外界的交互便變成了適配器和應(yīng)用層之間的交互,如上圖所示。

 

從圖中可以看出,領(lǐng)域模型位于應(yīng)用程序的核心部分,外界與領(lǐng)域模型的交互都通過(guò)應(yīng)用層完成,應(yīng)用層是領(lǐng)域模型的直接客戶。然而,應(yīng)用層中不應(yīng)該包含有業(yè)務(wù)邏輯,否則就造成了領(lǐng)域邏輯的泄漏,而應(yīng)該是很薄的一層,主要起到協(xié)調(diào)的作用,它所做的只是將業(yè)務(wù)操作代理給我們的領(lǐng)域模型。同時(shí),如果我們的業(yè)務(wù)操作有事務(wù)需求,那么對(duì)于事務(wù)的管理應(yīng)該放在應(yīng)用層上,因?yàn)槭聞?wù)也是以業(yè)務(wù)用例為單位的。

 

應(yīng)用層雖然很薄,但卻非常重要,因?yàn)檐浖到y(tǒng)的領(lǐng)域邏輯都是通過(guò)它暴露出去的,此時(shí)的應(yīng)用層扮演了系統(tǒng)門(mén)面(Facade)的角色。

 

DDD之戰(zhàn)術(shù)設(shè)計(jì)

戰(zhàn)略設(shè)計(jì)為我們提供一種高層視野來(lái)審視我們的軟件系統(tǒng),而戰(zhàn)術(shù)設(shè)計(jì)則將戰(zhàn)略設(shè)計(jì)進(jìn)行具體化和細(xì)節(jié)化,它主要關(guān)注的是技術(shù)層面的實(shí)施,也是對(duì)我們程序員來(lái)得最實(shí)在的地方。

 

行為飽滿的領(lǐng)域?qū)ο?/h3>

我們希望領(lǐng)域?qū)ο竽軌驕?zhǔn)確地表達(dá)出業(yè)務(wù)意圖,但是多數(shù)時(shí)候,我們所看到的卻是充滿getter和setter的領(lǐng)域?qū)ο?,此時(shí)的領(lǐng)域?qū)ο笠呀?jīng)不是領(lǐng)域?qū)ο罅?,而是Martin Fowler所稱(chēng)之為的貧血對(duì)象。

 

放到Java世界中,多年以來(lái),Java Bean規(guī)范都引誘著程序員們以“自然而然又合乎情理”的方式創(chuàng)建著無(wú)數(shù)的貧血對(duì)象,而一些框架也規(guī)定對(duì)象必須提供getter和setter方法,比如Hibernate的早期版本。那么,貧血對(duì)象到底有什么壞處呢?來(lái)看一個(gè)例子:要修改一個(gè)客戶(Customer)的郵箱地址,在使用setter方法時(shí)為:

public class Customer { private String email; public void setEmail(String email) { this.email = email; }}

 

雖然以上代碼可以完成“修改郵箱地址”的功能,但是當(dāng)你讀到這段代碼時(shí),你能否推測(cè)出系統(tǒng)中就一定存在著一個(gè)“修改郵箱地址”的業(yè)務(wù)用例呢?

 

你可能會(huì)說(shuō),可以在另一個(gè)Service類(lèi)里面創(chuàng)建一個(gè)changeCustomerEmail()方法,再在該方法中調(diào)用Customer的setEmailAddress()方法,這樣業(yè)務(wù)意圖不就明了了嗎?問(wèn)題在于,修改郵箱地址這樣的職責(zé)本來(lái)就應(yīng)該放在Customer上,而不應(yīng)該由Service和Customer共同完成。遵循諸如信息封裝這樣的基本面向?qū)ο笤瓌t是在實(shí)施DDD時(shí)最基本的素養(yǎng)。

 

要?jiǎng)?chuàng)建行為飽滿的領(lǐng)域?qū)ο蟛⒉浑y,我們需要轉(zhuǎn)變一下思維,將領(lǐng)域?qū)ο螽?dāng)做是服務(wù)的提供方,而不是數(shù)據(jù)容器,多思考一個(gè)領(lǐng)域?qū)ο竽軌蛱峁┠男┬袨?,而不是?shù)據(jù)。

 

近幾年又重新流行起來(lái)的函數(shù)式編程也能夠幫助我們編寫(xiě)更加具有業(yè)務(wù)表達(dá)力的業(yè)務(wù)代碼,比如C#和Java 8都提供了Lambda功能,同時(shí)還包括多數(shù)動(dòng)態(tài)語(yǔ)言(比如Ruby和Groovy等)。再進(jìn)一步,我們完全可以通過(guò)領(lǐng)域特定語(yǔ)言(DSL)的方式實(shí)現(xiàn)領(lǐng)域模型。

 

筆者曾經(jīng)設(shè)想過(guò)這么一個(gè)軟件系統(tǒng):它的核心功能完全由一套DSL暴露給外界,所有業(yè)務(wù)操作都通過(guò)這套DSL進(jìn)行,這個(gè)領(lǐng)域的業(yè)務(wù)規(guī)則可以通過(guò)一套規(guī)則引擎進(jìn)行配置,于是這套DSL可以像上文提到的知識(shí)產(chǎn)權(quán)核一樣拿到市面上進(jìn)行銷(xiāo)售。此時(shí),我們的核心域被嚴(yán)嚴(yán)實(shí)實(shí)地封裝在這套DSL之內(nèi),不容許外界的任何污染。

 

實(shí)體vs值對(duì)象(Entity vs Value Object)

在一個(gè)軟件系統(tǒng)中,實(shí)體表示那些具有生命周期并且會(huì)在其生命周期中發(fā)生改變的東西;而值對(duì)象則表示起描述性作用的并且可以相互替換的概念。同一個(gè)概念,在一個(gè)軟件系統(tǒng)中被建模成了實(shí)體,但是在另一個(gè)系統(tǒng)中則有可能是值對(duì)象。例如貨幣,在通常交易中,我們都將它建模成了一個(gè)值對(duì)象,因?yàn)槲覀兓?0元買(mǎi)了一本書(shū),我們只是關(guān)心貨幣的數(shù)量而已,而不是關(guān)心具體使用了哪一張20元的鈔票,也即兩張20元的鈔票是可以互換的。但是,如果現(xiàn)在中國(guó)人民銀行需要建立一個(gè)系統(tǒng)來(lái)管理所有發(fā)行的貨幣,并且希望對(duì)每一張貨幣進(jìn)行跟蹤,那么此時(shí)的貨幣便變成了一個(gè)實(shí)體,并且具有唯一標(biāo)識(shí)(Identity)。在這個(gè)系統(tǒng)中,即便兩張鈔票都是20元,他們依然表示兩個(gè)不同的實(shí)體。

 

具體到實(shí)現(xiàn)層面,值對(duì)象是沒(méi)有唯一標(biāo)識(shí)的,他的equals()方法(比如在Java語(yǔ)言中)可以用它所包含的描述性屬性字段來(lái)實(shí)現(xiàn)。但是,對(duì)于實(shí)體而言,equals()方法便只能通過(guò)唯一標(biāo)識(shí)來(lái)實(shí)現(xiàn)了,因?yàn)榧幢銉蓚€(gè)實(shí)體所擁有的狀態(tài)是一樣的,他們依然是不同的實(shí)體,就像兩個(gè)人的名字都叫張三,但是他們卻是兩個(gè)不同的人的個(gè)體。

 

我們發(fā)現(xiàn),多數(shù)領(lǐng)域概念都可以建模成值對(duì)象,而非實(shí)體。值對(duì)象就像軟件系統(tǒng)中的過(guò)客一樣,具有“創(chuàng)建后不管”的特征,因此,我們不需要像關(guān)心實(shí)體那樣去關(guān)心諸如生命周期和持久化等問(wèn)題。

 

聚合(Aggregate)

聚合可能是DDD中最難理解的概念 ,之所以稱(chēng)之為聚合,是因?yàn)榫酆现兴膶?duì)象之間具有密不可分的聯(lián)系,他們是內(nèi)聚在一起的。比如一輛汽車(chē)(Car)包含了引擎(Engine)、車(chē)輪(Wheel)和油箱(Tank)等組件,缺一不可。一個(gè)聚合中可以包含多個(gè)實(shí)體和值對(duì)象,因此聚合也被稱(chēng)為根實(shí)體。聚合是持久化的基本單位,它和資源庫(kù)(請(qǐng)參考下文)具有一一對(duì)應(yīng)的關(guān)系。

 

既然聚合可以容納其他領(lǐng)域?qū)ο?,那么聚合?yīng)該設(shè)計(jì)得多大呢?這也是設(shè)計(jì)聚合的難點(diǎn)之一。比如在一個(gè)博客(Blog)系統(tǒng)中,一個(gè)用戶(User)可以創(chuàng)建多個(gè)Blog,而一個(gè)Blog又可以包含多篇博文(Post)。在建模時(shí),我們通常的做法是在User對(duì)象中包含一個(gè)Blog的集合,然后在每個(gè)Blog中又包含了一個(gè)Post的集合。你真的需要這么做嗎?如果你需要修改User的基本信息,在加載User時(shí),所有的Blog和Post也需要加載,這將造成很大的性能損耗。誠(chéng)然,我們可以通過(guò)延遲加載的方式解決問(wèn)題,但是延遲加載只是技術(shù)上的實(shí)現(xiàn)方式而已。導(dǎo)致上述問(wèn)題的深層原因其實(shí)在我們的設(shè)計(jì)上,我們發(fā)現(xiàn),User更多的是和認(rèn)證授權(quán)相關(guān)的概念,而與Blog關(guān)系并不大,因此完全沒(méi)有必要在User中維護(hù)Blog的集合。在將User和Blog分離之后,Blog也和User一樣成為了一個(gè)聚合,它擁有自己的資源庫(kù)。問(wèn)題又來(lái)了:既然User和Blog分離了,那么如果需要在Blog中引用User又該怎么辦呢?在一個(gè)聚合中直接引用另外一個(gè)聚合并不是DDD所鼓勵(lì)的,但是我們可以通過(guò)ID的方式引用另外的聚合,比如在Blog中可以維護(hù)一個(gè)userId的實(shí)例變量。

 

User作為Blog的創(chuàng)建者,可以成為Blog的工廠。放到DDD中,創(chuàng)建Blog的功能也只能由User完成。

 

綜上,對(duì)于“創(chuàng)建Blog”的用例,我們可以通過(guò)以下方法完成:

public class BlogApplicatioinService { @Transactional public void createBlog(String blogName, String userId) { User user = userRepository.userById(userId); Blog blog = user.createBlog(blogName); blogRepository.save(blog); }}

在上例中,業(yè)務(wù)用例通過(guò)BlogApplicationService應(yīng)用服務(wù)完成,在用例方法createBlog()中,首先通過(guò)User的資源庫(kù)得到一個(gè)User,然后調(diào)用User中的工廠方法createBlog()方法創(chuàng)建一個(gè)Blog,最后通過(guò)BlogRepository對(duì)Blog進(jìn)行持久化。整個(gè)過(guò)程構(gòu)成了一次事務(wù),因此createBlog()方法標(biāo)記有@Transactional作為事務(wù)邊界。

 

使用聚合的首要原則為在一次事務(wù)中,最多只能更改一個(gè)聚合的狀態(tài)。如果一次業(yè)務(wù)操作涉及到了對(duì)多個(gè)聚合狀態(tài)的更改,那么應(yīng)該采用發(fā)布領(lǐng)域事件(參考下文)的方式通知相應(yīng)的聚合。此時(shí)的數(shù)據(jù)一致性便從事務(wù)一致性變成了最終一致性(Eventual Consistency)。

 

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

你是否遇到過(guò)這樣的問(wèn)題:想建模一個(gè)領(lǐng)域概念,把它放在實(shí)體上不合適,把它放在值對(duì)象上也不合適,然后你冥思苦想著自己的建模方式是不是出了問(wèn)題。恭喜你,祝賀你,你的建模手法完全沒(méi)有問(wèn)題,只是你還沒(méi)有接觸到領(lǐng)域服務(wù)(Domain Service)這個(gè)概念,因?yàn)轭I(lǐng)域服務(wù)本來(lái)就是來(lái)處理這種場(chǎng)景的。比如,要對(duì)密碼進(jìn)行加密,我們便可以創(chuàng)建一個(gè)PasswordEncryptService來(lái)專(zhuān)門(mén)負(fù)責(zé)此事。

 

值得一提的是,領(lǐng)域服務(wù)和上文中提到的應(yīng)用服務(wù)是不同的,領(lǐng)域服務(wù)是領(lǐng)域模型的一部分,而應(yīng)用服務(wù)不是。應(yīng)用服務(wù)是領(lǐng)域服務(wù)的客戶,它將領(lǐng)域模型變成對(duì)外界可用的軟件系統(tǒng)。

 

領(lǐng)域服務(wù)不能濫用,因?yàn)槿绻覀儗⑻嗟念I(lǐng)域邏輯放在領(lǐng)域服務(wù)上,實(shí)體和值對(duì)象上將變成貧血對(duì)象。

 

資源庫(kù)(Repository)

資源庫(kù)用于保存和獲取聚合對(duì)象,在這一點(diǎn)上,資源庫(kù)與DAO多少有些相似之處。但是,資源庫(kù)和DAO是存在顯著區(qū)別的。DAO只是對(duì)數(shù)據(jù)庫(kù)的一層很薄的封裝,而資源庫(kù)則更加具有領(lǐng)域特征。另外,所有的實(shí)體都可以有相應(yīng)的DAO,但并不是所有的實(shí)體都有資源庫(kù),只有聚合才有相應(yīng)的資源庫(kù)。

 

資源庫(kù)分為兩種,一種是基于集合的,一種是基于持久化的。顧名思義,基于集合的資源庫(kù)具有編程語(yǔ)言中集合的特征。舉個(gè)例子,Java中的List,我們從一個(gè)List中取出一個(gè)元素,在對(duì)該元素進(jìn)行修改之后,我們并不用顯式地將該元素重新保存到List里面。因此,面向集合的資源庫(kù)并不存在save()方法。比如,對(duì)于上文中的User,其資源庫(kù)可以設(shè)計(jì)為:

public interface CollectionOrientedUserRepository { public void add(User user); public User userById(String userId); public List allUsers(); public void remove(User user); }


對(duì)于面向持久化的資源庫(kù)來(lái)說(shuō),在對(duì)聚合進(jìn)行修改之后,我們需要顯式地調(diào)用sava()方法將其更新到資源庫(kù)中。依然是User,此時(shí)的資源庫(kù)如下:

public interface PersistenceOrientedUserRepository { public void save(User user); public User userById(String userId); public List allUsers(); public void remove(User user); }

 

在以上兩種方式所實(shí)現(xiàn)的資源庫(kù)中,雖然只是將add()方法改成了save()方法,但是在使用的時(shí)候卻是不一樣的。在使用面向集合資源庫(kù)時(shí),add()方法只是用來(lái)將新的聚合加入資源庫(kù);而在面向持久化的資源庫(kù)中,save()方法不僅用于添加新的聚合,還用于顯式地更新既有聚合。

 

領(lǐng)域事件(Domain Event)

在Eric的《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》中并沒(méi)有提到領(lǐng)域事件,領(lǐng)域事件是最近幾年才加入DDD生態(tài)系統(tǒng)的。

 

在傳統(tǒng)的軟件系統(tǒng)中,對(duì)數(shù)據(jù)一致性的處理都是通過(guò)事務(wù)完成的,其中包括本地事務(wù)和全局事務(wù)。但是,DDD的一個(gè)重要原則便是一次事務(wù)只能更新一個(gè)聚合實(shí)例。然而,的確存在需要修改多個(gè)聚合的業(yè)務(wù)用例,那么此時(shí)我們應(yīng)該怎么辦呢?

 

另外,在最近流行起來(lái)的微服務(wù)(Micro Service)的架構(gòu)中,整個(gè)系統(tǒng)被分成了很多個(gè)輕量的程序模塊,他們之間的數(shù)據(jù)一致性并不容易通過(guò)事務(wù)一致性完成,此時(shí)我們又該怎么辦呢?

 

在DDD中,領(lǐng)域事件便可以用于處理上述問(wèn)題,此時(shí)最終一致性取代了事務(wù)一致性,通過(guò)領(lǐng)域事件的方式達(dá)到各個(gè)組件之間的數(shù)據(jù)一致性。

 

領(lǐng)域事件的命名遵循英語(yǔ)中的“名詞+動(dòng)詞過(guò)去分詞”格式,即表示的是先前發(fā)生過(guò)的一件事情。比如,購(gòu)買(mǎi)者提交商品訂單之后發(fā)布OrderSubmitted事件,用戶更改郵箱地址之后發(fā)布EmailAddressChanged事件。

 

需要注意的是,既然是領(lǐng)域事件,他們便應(yīng)該從領(lǐng)域模型中發(fā)布。領(lǐng)域事件的最終接收者可以是本限界上下文中的組件,也可以是另一個(gè)限界上下文。

 

領(lǐng)域事件的額外好處在于它可以記錄發(fā)生在軟件系統(tǒng)中所有的重要修改,這樣可以很好地支持程序調(diào)試和商業(yè)智能化。另外,在CQRS架構(gòu)的軟件系統(tǒng)中,領(lǐng)域事件還用于寫(xiě)模型和讀模型之間的數(shù)據(jù)同步。再進(jìn)一步發(fā)展,事件驅(qū)動(dòng)架構(gòu)可以演變成事件源(Event Sourcing),即對(duì)聚合的獲取并不是通過(guò)加載數(shù)據(jù)庫(kù)中的瞬時(shí)狀態(tài),而是通過(guò)重放發(fā)生在聚合生命周期中的所有領(lǐng)域事件完成。

 

總結(jié)

DDD存在戰(zhàn)略設(shè)計(jì)和戰(zhàn)術(shù)設(shè)計(jì)之分,過(guò)度地強(qiáng)調(diào)DDD的技術(shù)性將使我們錯(cuò)過(guò)由戰(zhàn)略設(shè)計(jì)帶來(lái)的好處。因此,在實(shí)現(xiàn)DDD時(shí),我們應(yīng)該將戰(zhàn)略設(shè)計(jì)也放在一個(gè)重要的位置加以對(duì)待。戰(zhàn)略設(shè)計(jì)幫助我們從一個(gè)宏觀的角度觀察和審視軟件系統(tǒng),其中的限界上下文和上下文映射圖幫助我們正確地界分各個(gè)子域(系統(tǒng))。DDD的戰(zhàn)術(shù)設(shè)計(jì)則更加側(cè)重于技術(shù)實(shí)現(xiàn),它向我們提供了一整套技術(shù)工具集,包括實(shí)體、值對(duì)象、領(lǐng)域服務(wù)和資源庫(kù)等。雖然DDD的概念已經(jīng)提出近10年了,但是在如何實(shí)現(xiàn)DDD上,我們依然有很長(zhǎng)的路要走。

作者:Leo_wl
    
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
基于DDD的微服務(wù)設(shè)計(jì)和開(kāi)發(fā)實(shí)戰(zhàn)
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)理解&總結(jié)
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)之理論篇:應(yīng)對(duì)復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道 - InfoQ
利用Spring Cloud實(shí)現(xiàn)微服務(wù)(二)
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)在馬蜂窩優(yōu)惠中心重構(gòu)中的實(shí)踐
從MVC到DDD的架構(gòu)演進(jìn)
更多類(lèi)似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服