
Maven Module,但實(shí)際上可以合并到 DAO 層。
2.1 第一步、數(shù)據(jù)模型與DAO層合并
2.2 第二步、Service層抽取業(yè)務(wù)邏輯
public class Service {
@Transactional
public void bizLogic(Param param) {
checkParam(param);//校驗(yàn)不通過(guò)則拋出自定義的運(yùn)行時(shí)異常
Data data = new Data();//或者是mapper.queryOne(param);
data.setId(param.getId());
if (condition1 == true) {
biz1 = biz1(param.getProperty1());
data.setProperty1(biz1);
} else {
biz1 = biz11(param.getProperty1());
data.setProperty1(biz1);
}
if (condition2 == true) {
biz2 = biz2(param.getProperty2());
data.setProperty2(biz2);
} else {
biz2 = biz22(param.getProperty2());
data.setProperty2(biz2);
}
//省略一堆set方法
mapper.updateXXXById(data);
}
}
public class Service {
public void bizLogic(Param param) {
//如果校驗(yàn)不通過(guò),則拋一個(gè)運(yùn)行時(shí)異常
checkParam(param);
//加載模型
Domain domain = loadDomain(param);
//調(diào)用外部服務(wù)取值
SomeValue someValue=this.getSomeValueFromOtherService(param.getProperty2());
//模型自己去做業(yè)務(wù)邏輯,Service不關(guān)心模型內(nèi)部的業(yè)務(wù)規(guī)則
domain.doBusinessLogic(param.getProperty1(), someValue);
//保存模型
saveDomain(domain);
}
}

2.3 第三步、維護(hù)領(lǐng)域?qū)ο笊芷?/h2>
在上一步中,loadDomain、saveDomain 這兩個(gè)方法還沒(méi)有得到討論,這兩個(gè)方法跟領(lǐng)域?qū)ο蟮纳芷谙⑾⑾嚓P(guān)。 關(guān)于領(lǐng)域?qū)ο蟮纳芷诘脑敿?xì)知識(shí),讀者可以自行學(xué)習(xí)了解。 不管是 loadDomain 還是 saveDomain,我們一般都要依賴(lài)于數(shù)據(jù)庫(kù),所以這兩個(gè)方法對(duì)應(yīng)的邏輯,肯定是要跟 DAO 產(chǎn)生聯(lián)系的。 保存或者加載領(lǐng)域模型,我們可以抽象成一種組件,通過(guò)這種組件進(jìn)行封裝模型加載、保存的操作,這種組件就是Repository。 注意,Repository 是對(duì)加載或者保存領(lǐng)域模型(這里指的是聚合根,因?yàn)橹挥芯酆细艜?huì)有Repository)的抽象,必須對(duì)上層屏蔽領(lǐng)域模型持久化的細(xì)節(jié),因此其方法的入?yún)⒒蛘叱鰠?,一定是基本?shù)據(jù)類(lèi)型或者領(lǐng)域模型,不能是數(shù)據(jù)庫(kù)表對(duì)應(yīng)的數(shù)據(jù)模型。 以下是 Repository 的偽代碼:
public interface DomainRepository {
void save(AggregateRoot root);
AggregateRoot load(EntityId id);
}
接下來(lái)我們要考慮在哪里實(shí)現(xiàn)DomainRepository。既然 DomainRepository 與底層數(shù)據(jù)庫(kù)有關(guān)聯(lián),但是我們現(xiàn)在 DAO 層并沒(méi)有引入 Domain 這個(gè)包,DAO 層自然無(wú)法提供 DomainRepository的實(shí)現(xiàn),我們初步考慮是不是可以將 DomainRepository 實(shí)現(xiàn)在 Service 層。 但是,如果我們?cè)?Service 中實(shí)現(xiàn)DomainRepository,勢(shì)必需要在 Service 層操作數(shù)據(jù)模型:查詢(xún)出來(lái)數(shù)據(jù)模型再封裝為領(lǐng)域模型、或者將領(lǐng)域模型轉(zhuǎn)為數(shù)據(jù)模型再通過(guò)ORM 保存,這個(gè)過(guò)程不該是 Service 層關(guān)心的。 因此,我們決定在 DAO 層直接引入 Domain 包,并在 DAO 層提供 DomainRepository 接口的實(shí)現(xiàn),DAO 層查詢(xún)出數(shù)據(jù)模型之后,封裝成領(lǐng)域模型供DomainRepository 返回。 這樣調(diào)整之后, DAO 層不再向 Service 返回?cái)?shù)據(jù)模型,而是返回領(lǐng)域模型,這就隱藏了數(shù)據(jù)庫(kù)交互的細(xì)節(jié),我們也把DAO層換個(gè)名字稱(chēng)之為Repository。 現(xiàn)在,我們項(xiàng)目的架構(gòu)圖是這樣的了: 
由于數(shù)據(jù)模型屬于貧血模型,自身沒(méi)有業(yè)務(wù)邏輯,并且只有Repository這個(gè)包會(huì)用到,因此我們將之合并到Repository中,接下來(lái)不再單獨(dú)列舉。
2.4 第四步、泛化抽象
在第三步中,我們的架構(gòu)圖已經(jīng)跟經(jīng)典四層架構(gòu)非常相似了,我們?cè)賹?duì)某些層進(jìn)行泛化抽象。
-
Infrastructure
Repository 倉(cāng)儲(chǔ)層其實(shí)屬于基礎(chǔ)設(shè)施層,只不過(guò)其職責(zé)是持久化和加載聚合,所以,我們將 Repository層改名為 infrastructure-persistence,可以理解為基礎(chǔ)設(shè)施層持久化包。 之所以采取這種 infrastructure-XXX 的格式進(jìn)行命名,是由于 Infrastructure 可能會(huì)有很多的包,分別提供不同的基礎(chǔ)設(shè)施支持。 例如:一般的項(xiàng)目,還有可能需要引入緩存,我們就可以再加一個(gè)包,名字叫infrastructure-cache。 對(duì)于外部的調(diào)用,DDD中有防腐層的概念,將外部模型通過(guò)防腐層進(jìn)行隔離,避免污染本地上下文的領(lǐng)域模型。我們使用入口(Gateway)來(lái)封裝對(duì)外部系統(tǒng)或資源的訪(fǎng)問(wèn)(詳細(xì)見(jiàn)《企業(yè)應(yīng)用架構(gòu)模式》,18.1入口(Gateway)),因此將對(duì)外調(diào)用這一層稱(chēng)之為infrastructure-gateway。
注意:Infrastructure 層的門(mén)面接口都應(yīng)先在Domain 層定義,其方法的入?yún)?、出參,都?yīng)該是領(lǐng)域模型(實(shí)體、值對(duì)象)或者基本類(lèi)型。
-
User Interface
Controller 層其實(shí)就是用戶(hù)接口層,即 User Interface 層,我們?cè)陧?xiàng)目簡(jiǎn)稱(chēng) ui。當(dāng)然了可能很多開(kāi)發(fā)者會(huì)覺(jué)得叫UI好像很別扭,認(rèn)為 UI就是 UI 設(shè)計(jì)師設(shè)計(jì)的圖形界面。 Controller 層的名字有很多,有的叫 Rest,有的叫 Resource,考慮到我們這一層不只是有 Rest 接口,還可能還有一系列 Web相關(guān)的攔截器,所以我一般稱(chēng)之為 Web。因此,我們將其改名為 ui-web,即用戶(hù)接口層的 Web 包。 同樣,我們可能會(huì)有很多的用戶(hù)接口,但是他們通過(guò)不同的協(xié)議對(duì)外提供服務(wù),因而被劃分到不同的包中。 我們?nèi)绻袑?duì)外提供的 RPC服務(wù),那么其服務(wù)實(shí)現(xiàn)類(lèi)所在的包就可以命名為 ui-provider。 有時(shí)候引入某個(gè)中間件會(huì)同時(shí)增加 Infrastructure 和 User Interface。 例如,如果引入 Kafka 就需要考慮一下,如果是給 Service 層提供調(diào)用的,例如邏輯執(zhí)行完發(fā)送消息通知下游,那么我們?cè)偌右粋€(gè)包infrastructure-publisher;如果是消費(fèi) Kafka 的消息,然后調(diào)用 Service 層執(zhí)行業(yè)務(wù)邏輯的,那么就可以命名為 ui-subscriber。
-
Application
至此,Service 層目前已經(jīng)沒(méi)有業(yè)務(wù)邏輯了,業(yè)務(wù)邏輯都在 Domain 層去執(zhí)行了,Service 只是協(xié)調(diào)領(lǐng)域模型、基礎(chǔ)設(shè)施層完成業(yè)務(wù)邏輯。 所以,我們把 Service 層改名為 Application Service 層。 經(jīng)過(guò)第四步的抽象,其架構(gòu)圖為: 
2.5 第五步、完整的包結(jié)構(gòu)
我們繼續(xù)對(duì)第四步中出現(xiàn)的包進(jìn)行整理,此時(shí)還需要考慮一個(gè)問(wèn)題,我們的啟動(dòng)類(lèi)應(yīng)該放在哪里? 由于有很多的 User Interface,所以啟動(dòng)類(lèi)放在任意一個(gè)User Interface中都不合適,放置在A(yíng)pplication Service中也不合適,因此,啟動(dòng)類(lèi)應(yīng)該存放在單獨(dú)的模塊中。又因?yàn)?application這個(gè)名字被應(yīng)用層占用了,所以將啟動(dòng)類(lèi)所在的模塊命名為 launcher,一個(gè)項(xiàng)目可以存在多個(gè)launcher,按需引用User Interface。 加入啟動(dòng)包,我們就得到了完整的 maven 包結(jié)構(gòu)。 包結(jié)構(gòu)如圖所示: 
至此,DDD 項(xiàng)目的整體結(jié)構(gòu)基本講完了。 2.6 精煉后的思考
在經(jīng)過(guò)前面五步精煉得到這個(gè)架構(gòu)圖中,經(jīng)典四層架構(gòu)的四層都出現(xiàn)了,而且長(zhǎng)得跟六邊形架構(gòu)也很像。這是為什么呢? 其實(shí),不管是經(jīng)典四層架構(gòu)、還是六邊形架構(gòu),亦或者整潔架構(gòu),都是對(duì)系統(tǒng)應(yīng)用的描述,也許描述的側(cè)重點(diǎn)不一樣,但是描述的是同一個(gè)事物。既然描述的是同一個(gè)事物,長(zhǎng)得像才是理所當(dāng)然的,不可能只是換一個(gè)描述方式,系統(tǒng)就從根本上發(fā)生了改變。 對(duì)于任何一個(gè)應(yīng)用,都可以看成“輸入-處理-輸出”的過(guò)程。 “輸入”環(huán)節(jié):通過(guò)某種協(xié)議對(duì)外暴露領(lǐng)域的能力,這些協(xié)議可能是 REST、可能是 RPC、可能是 MQ 的訂閱者,也可能是WebSocket,也可能是一些任務(wù)調(diào)度的 Task; ”處理“環(huán)節(jié):處理環(huán)節(jié)是整個(gè)應(yīng)用的核心,代表了應(yīng)用具備的核心能力,是應(yīng)用的價(jià)值所在,應(yīng)用在這個(gè)環(huán)節(jié)執(zhí)行業(yè)務(wù)邏輯,貧血模型由Service執(zhí)行業(yè)務(wù)處理,充血模型則是由模型進(jìn)行業(yè)務(wù)處理。 “輸出”環(huán)節(jié),業(yè)務(wù)邏輯執(zhí)行完成之后將結(jié)果輸出到外部。 不管我們采用的什么架構(gòu),其描述的應(yīng)用的核心都是這個(gè)過(guò)程,不必生搬硬套非得用什么應(yīng)用架構(gòu)。 正如《金剛經(jīng)》所言:一切有為法,如夢(mèng)幻泡影,如露亦如電,應(yīng)作如是觀(guān);凡所有相,皆是虛妄;若見(jiàn)諸相非相,即見(jiàn)如來(lái)。 三、ddd-archetype 3.1 Maven Archetype介紹
Maven Archetype是一個(gè)Maven插件,可以幫助開(kāi)發(fā)人員快速創(chuàng)建項(xiàng)目的基礎(chǔ)結(jié)構(gòu),大大減少開(kāi)發(fā)人員在創(chuàng)建項(xiàng)目時(shí)所需的時(shí)間和精力,并且可以確保項(xiàng)目結(jié)構(gòu)的一致性和可重用性,從而提高代碼質(zhì)量和可維護(hù)性。 我們?cè)诮榻BDDD應(yīng)用架構(gòu)時(shí),對(duì)項(xiàng)目的結(jié)構(gòu)進(jìn)行了介紹。我們將項(xiàng)目分為多個(gè)Maven Module,如果每個(gè)項(xiàng)目都手工創(chuàng)建一次,是比較繁瑣的工作,也不利項(xiàng)目結(jié)構(gòu)的統(tǒng)一。 我們使用Maven Archetype創(chuàng)建DDD項(xiàng)目初始化的腳手架,使其在初始化時(shí)完整實(shí)現(xiàn)上文第五步的應(yīng)用架構(gòu)。 3.2 ddd-archetype的使用
3.2.1 項(xiàng)目介紹
ddd-archetype是一個(gè)Maven Archetype的原型工程,我們將其克隆到本地之后,可以安裝為Maven Archetype,幫助我們快速創(chuàng)建DDD項(xiàng)目腳手架。 項(xiàng)目鏈接:https://github.com/feiniaojin/ddd-archetype 3.2.2 安裝過(guò)程
以下將以IDEA為例展示ddd-archetype的安裝使用過(guò)程,主要過(guò)程是: 克隆項(xiàng)目-->archetype:create-from-project-->install-->archetype:crawl 3.2.3 克隆項(xiàng)目
將項(xiàng)目克隆到本地: git clone https://github.com/feiniaojin/ddd-archetype.git
直接使用主分支即可,然后使用IDEA打開(kāi)該項(xiàng)目 
3.2.4 archetype:create-from-project
配置打開(kāi)IDEA的run/debug configurations窗口,如下:

選擇add new configurations,彈出以下窗口: 其中,上圖中1~4各個(gè)標(biāo)識(shí)的值為: 標(biāo)識(shí)1 - 選擇"+"號(hào); 標(biāo)識(shí)2?- 選擇"Maven"; 標(biāo)識(shí)3 - 命令為: archetype:create-from-project -Darchetype.properties=archetype.properties
注意,在IDEA中添加的命令默認(rèn)不需要加mvn
標(biāo)識(shí)4?- 選擇ddd-archetype的根目錄 以上配置完成后,點(diǎn)擊執(zhí)行該命令。 3.2.5 install
上一步執(zhí)行完成且無(wú)報(bào)錯(cuò)之后,配置install命令。 
其中,上圖中1~2各個(gè)標(biāo)識(shí)的值為: 標(biāo)識(shí)1 - 值為install; 標(biāo)識(shí)2 - 值為上一步運(yùn)行的結(jié)果,路徑為: ddd-archetype/target/generated-sources/archetype
install配置完成之后,點(diǎn)擊執(zhí)行。 3.2.6 archetype:crawl
install執(zhí)行完成且無(wú)報(bào)錯(cuò),接著配置archetype:crawl命令。 
其中,標(biāo)識(shí)1中的值為: archetype:crawl
配置完成,點(diǎn)擊執(zhí)行即可。 3.3 使用ddd-archetype初始化項(xiàng)目
-
創(chuàng)建項(xiàng)目時(shí),點(diǎn)擊manage catalogs:

-
將本地的maven私服中的archetype-catalog.xml加入到catalogs中:

添加成功,如下: 創(chuàng)建項(xiàng)目時(shí),選擇本地archetype-catalog,并且選擇ddd-archetype,填入項(xiàng)目信息并創(chuàng)建項(xiàng)目: 
項(xiàng)目創(chuàng)建完成后: 
四、代碼案例 本文提供了配套的代碼案例,該案例使用DDD和本文的應(yīng)用架構(gòu)實(shí)現(xiàn)了簡(jiǎn)單的CMS系統(tǒng)。案例項(xiàng)目采用前后端分離的方式,因此有后端和前端兩個(gè)代碼庫(kù)。 4.1 后端
后端項(xiàng)目使用本文的ddd-archetype創(chuàng)建,實(shí)現(xiàn)了部分CMS的功能,并落地部分DDD的概念。 GitHub鏈接:https://github.com/feiniaojin/ddd-example-cms 
實(shí)現(xiàn)的DDD概念有:實(shí)體、值對(duì)象、聚合根、Factory、Repository、CQRS。 技術(shù)棧:
-
Spring Boot
-
H2內(nèi)存數(shù)據(jù)庫(kù)
-
Spring Data JDBC
無(wú)外部中間件依賴(lài) ,clone到本地即可編譯運(yùn)行,非常方便。 4.2 前端
前端項(xiàng)目基于vue-element-admin開(kāi)發(fā),詳細(xì)安裝方式見(jiàn)代碼庫(kù)的README。 GitHub鏈接:https://github.com/feiniaojin/ddd-example-cms-front 
4.3 運(yùn)行截圖

五、總結(jié)以及進(jìn)一步學(xué)習(xí) 本文通過(guò)對(duì)貧血三層架構(gòu)進(jìn)行精煉,推導(dǎo)出適合我們落地的應(yīng)用架構(gòu),并且將之實(shí)現(xiàn)為Maven Archetype以應(yīng)用到實(shí)際開(kāi)發(fā),然而應(yīng)用架構(gòu)只是落地DDD的一個(gè)知識(shí)點(diǎn),要完整落地DDD還必須體系化地掌握限界上下文、上下文映射、充血模型、實(shí)體、值對(duì)象、領(lǐng)域服務(wù)、Factory、Repository等知識(shí)點(diǎn)。
public interface DomainRepository {
void save(AggregateRoot root);
AggregateRoot load(EntityId id);
}

由于數(shù)據(jù)模型屬于貧血模型,自身沒(méi)有業(yè)務(wù)邏輯,并且只有Repository這個(gè)包會(huì)用到,因此我們將之合并到Repository中,接下來(lái)不再單獨(dú)列舉。
注意:Infrastructure 層的門(mén)面接口都應(yīng)先在Domain 層定義,其方法的入?yún)?、出參,都?yīng)該是領(lǐng)域模型(實(shí)體、值對(duì)象)或者基本類(lèi)型。


git clone https://github.com/feiniaojin/ddd-archetype.git


archetype:create-from-project -Darchetype.properties=archetype.properties
注意,在IDEA中添加的命令默認(rèn)不需要加mvn

ddd-archetype/target/generated-sources/archetype

archetype:crawl







-end-文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-597045.html
作者|覃玉杰文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-597045.html
到了這里,關(guān)于手把手教你落地DDD的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!