充血與貧血模型
不去具體講概念,感興趣的可以網(wǎng)上自己去找找理解一下。
1.1 還記得初學(xué)Java時(shí)那個(gè)動(dòng)物對(duì)象的例子嗎?
public class Dog {
private int age;
private String color;
public int getAge() {
return age;
}
//吃
public void eat(Object food) {
System.out.println("吃...");
}
//睡
public void rest() {
System.out.println("睡...");
}
}
對(duì)象是行為和屬性的封裝。上面的類定義沒(méi)毛病。簡(jiǎn)單點(diǎn)理解,所謂充血模型其實(shí)就是面象對(duì)象編程,這也是DDD所推崇的。沒(méi)有新鮮事,老外愛搞概念,愣是整出個(gè)充血跟DDD來(lái)。
1.2 貧血模型的例子
public class Dog {
private int age;
private String color;
private String status;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public class DogService {
//吃
public void eat(Dog dog, Object food) {
dog.setStatus("吃");
System.out.println(dog.toString() + food.toString());
}
//睡
public void rest(Dog dog) {
dog.setStatus("睡覺");
System.out.println(dog.toString() + " 睡覺覺...");
}
public Dog getDog(){
return new Dog();
}
}
public class DogController {
public void eat() {
Object food = new Object();
DogService ds = new DogService();
Dog dog = ds.getDog();
ds.eat(dog, food);
}
public void rest() {
DogService ds = new DogService();
Dog dog = ds.getDog();
ds.rest(dog);
}
}
看到了嗎? Dog類只有針對(duì)屬性的get,set操作,行為被搬到service類了.這種風(fēng)格是貧血模型的代表。缺點(diǎn):根據(jù)個(gè)人代碼風(fēng)格,有的controller類很重,有的service類很重。最重要的是如果不具備一定的領(lǐng)域分析的知識(shí),往往建出來(lái)的類似Dog的領(lǐng)域類是錯(cuò)誤的,甚至?xí)鶕?jù)經(jīng)驗(yàn)先建數(shù)據(jù)庫(kù)再建領(lǐng)域類。經(jīng)驗(yàn)豐富并且經(jīng)驗(yàn)是對(duì)的那還好,但如果經(jīng)驗(yàn)是錯(cuò)的呢。。。
2 Domain層實(shí)現(xiàn)
Domain層是具體的業(yè)務(wù)領(lǐng)域?qū)樱前l(fā)生業(yè)務(wù)變化最為頻繁的地方,是業(yè)務(wù)系統(tǒng)最核心的一層,是DDD關(guān)注的焦點(diǎn)和難點(diǎn)。這一層包含了如下一些domain object:entity、value object、domain event、domain service、factory、repository等
2.1 實(shí)體類Enity
領(lǐng)域?qū)嶓w是domain的核心成員。domain entity具有如下三個(gè)特征:
· 唯一業(yè)務(wù)標(biāo)識(shí)
· 持有自己的業(yè)務(wù)屬性和業(yè)務(wù)行為
· 屬性可變,有著自己的生命周期,故實(shí)體對(duì)象可能和它之前的狀態(tài)不一樣,但有同樣的唯一標(biāo)識(shí),是同一個(gè)實(shí)體.
生成唯一標(biāo)識(shí)的方法:
1,用戶提供 (但幾乎沒(méi)有人這樣用)
2, 應(yīng)用程序提供
3, 持久化機(jī)制提供 比如mysql主鍵自增 。通常不適合水平分庫(kù),各限界上下文會(huì)重復(fù)。

創(chuàng)建實(shí)體:
1)通過(guò)構(gòu)造函數(shù)

2) 通過(guò)工廠方法創(chuàng)建

實(shí)體類的校驗(yàn):
Strusts2框架有一個(gè)不錯(cuò)的validator接口,用于校驗(yàn)規(guī)則,它在調(diào)用控制器方法之前調(diào)用。如果自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的檢驗(yàn)框架呢?
1)定義一個(gè)抽象的校驗(yàn)類

2)定義具體的實(shí)現(xiàn)。

經(jīng)驗(yàn)分享:校驗(yàn)規(guī)則分內(nèi)凜規(guī)則和依賴規(guī)則。
內(nèi)凜規(guī)則是指那些自身需要必不可少的條件。如名字屬性的不為空及長(zhǎng)度等,年齡不能小于0。
依賴規(guī)則:比如請(qǐng)假需要依賴外部日歷實(shí)體對(duì)象等。
2.2 值對(duì)象Value Object
對(duì)照起來(lái)value object有如下特征:
· 可以有唯一業(yè)務(wù)標(biāo)識(shí) 【區(qū)別于domain entity】
· 持有自己的業(yè)務(wù)屬性和業(yè)務(wù)行為 【同domain entity】
· 一旦定義,他是不可變的,它通常是短暫的,這和java中的值對(duì)象(基本類型和String類型)類似 【區(qū)別于domain entity】
· 當(dāng)度量改變時(shí),可以用另一個(gè)對(duì)象替換。
· 不會(huì)對(duì)協(xié)作對(duì)象造成副作用。
· 值對(duì)象相等性比較。(實(shí)體對(duì)象比較沒(méi)有意義,每個(gè)實(shí)體對(duì)象有唯一值)
1,可替換性
如果一個(gè)值對(duì)象可以正常描述當(dāng)前狀態(tài),引用關(guān)系可以一值存在。否則可以用一個(gè)新的值對(duì)象替換.
理解:比如Customer有一個(gè)收貨地址Address,這個(gè)address可以建模值對(duì)象。換地址后這個(gè)值對(duì)象可替換。
2,值對(duì)象相等性
值對(duì)象全部屬性值相等時(shí),可以互換。
3,無(wú)副作用行為
一個(gè)對(duì)象方法可以設(shè)計(jì)為一個(gè)無(wú)副作用函數(shù)。它不修改對(duì)象本身的狀態(tài)。


widthMiddleInitial方法并沒(méi)有修改原來(lái)對(duì)象的firstname lastName屬性
不建議值對(duì)象方法參數(shù)里有實(shí)體對(duì)象,防止修改。
3 聚合
1, 啥是聚合?
理解一下聚合,其實(shí)是對(duì)象的依賴關(guān)系。
2,設(shè)計(jì)聚合時(shí)要考慮一致性.意味著一個(gè)客戶請(qǐng)求只在一個(gè)聚合實(shí)例上執(zhí)行一個(gè)命令方法.
3,聚合設(shè)計(jì)原則:設(shè)計(jì)小聚合。大的聚合即便能保證事務(wù)的一致性,也依然可能限制系統(tǒng)的性能可伸縮性。

一個(gè)龐大的聚合根還可能帶來(lái)性能損耗。可能需要加載許多關(guān)聯(lián)對(duì)象。
4.通過(guò)唯一標(biāo)識(shí)引用其它聚合。
聚合之間有依賴關(guān)系時(shí)不要直接寫依賴對(duì)象,通過(guò)唯一標(biāo)識(shí)來(lái)引用。通過(guò)標(biāo)識(shí)引用可以將不同限界上下文的分布式領(lǐng)域模型關(guān)聯(lián)起來(lái)。
5,在邊界之外使用最終一致性。當(dāng)一個(gè)聚合執(zhí)行命令方法時(shí),還需要在其它聚合上執(zhí)行任務(wù),使用最終一致性。一種實(shí)用的方法可以支持最終一致性,即一個(gè)聚合的命令方法發(fā)布的領(lǐng)域事件及時(shí)地發(fā)布給異步的訂閱方。
6,不要在聚合中注入資源庫(kù)和領(lǐng)域服務(wù)。
設(shè)計(jì)小的聚合要注意是否過(guò)度的小。重新設(shè)計(jì)的聚合根如下圖:

另一種設(shè)計(jì) -> Task也作為聚合根

不在同一事務(wù)中修改BacklogItem和Task.
實(shí)現(xiàn)最終一致性

實(shí)現(xiàn):
1,創(chuàng)建唯一標(biāo)識(shí)的根實(shí)體。
2,優(yōu)先使用值對(duì)象。
4 領(lǐng)域事件
1,啥是領(lǐng)域事件?
領(lǐng)域?qū)<宜P(guān)心的發(fā)生在領(lǐng)域中的一些事件。
將領(lǐng)域中所發(fā)生的活動(dòng)建模成一系列的離散事件。每個(gè)事件都用領(lǐng)域?qū)ο髞?lái)表示...領(lǐng)域事件是領(lǐng)域模型的組成部分,表示領(lǐng)域中所發(fā)生的事情
首先是解決領(lǐng)域的聚合性問(wèn)題。DDD中的聚合有一個(gè)原則是,在單個(gè)事務(wù)中,只允許對(duì)一個(gè)聚合對(duì)象進(jìn)行修改,由此產(chǎn)生的其他改變必須在單獨(dú)的事務(wù)中完成。如果一個(gè)業(yè)務(wù)跨多個(gè)聚合對(duì)象,領(lǐng)域事件會(huì)是一個(gè)不錯(cuò)的工具來(lái)解決這個(gè)問(wèn)題。通過(guò)領(lǐng)域事件的方式可以達(dá)到各個(gè)組件之間的數(shù)據(jù)一致性,通過(guò)最終一致性取代事務(wù)一致性。
其次領(lǐng)域事件也是一種領(lǐng)域分析的工具,有時(shí)從領(lǐng)域?qū)<业脑捴校覀兛床怀鲱I(lǐng)域事件的跡象,但是業(yè)務(wù)需求依然有可能需要領(lǐng)域事件。動(dòng)態(tài)流的事件模型加上結(jié)合DDD的聚合實(shí)體狀態(tài)和BC,可以有效進(jìn)行領(lǐng)域建模。
2,領(lǐng)域事件的技術(shù)實(shí)現(xiàn)
領(lǐng)域事件的技術(shù)實(shí)現(xiàn)實(shí)質(zhì)上觀察者模式的實(shí)現(xiàn)。技術(shù)的實(shí)現(xiàn)都好講,關(guān)鍵是理解觀察者模式在領(lǐng)域建模中的應(yīng)用場(chǎng)景。
3,領(lǐng)域事件需要關(guān)注的類容。
一,消息設(shè)施最終一致性。
二,事件存儲(chǔ):
1),將事件存儲(chǔ)作為一個(gè)消息隊(duì)列使用。
2),檢查由模型命令方法的所產(chǎn)生的所有結(jié)果的記錄
3),使用事件存儲(chǔ)中的數(shù)據(jù)進(jìn)行業(yè)務(wù)預(yù)測(cè)和分析。、
4),通過(guò)事件存儲(chǔ)重一個(gè)聚合。
5),撤銷對(duì)聚合的操作
4,轉(zhuǎn)發(fā)存儲(chǔ)的架構(gòu)風(fēng)格。
5 領(lǐng)域服務(wù)
1、領(lǐng)域服務(wù)表示一個(gè)無(wú)狀態(tài)的操作,強(qiáng)調(diào)一個(gè)無(wú)狀態(tài)的操作,狀態(tài)應(yīng)該在實(shí)體中維護(hù),領(lǐng)域服務(wù)處理是無(wú)狀態(tài)的邏輯過(guò)程;
2、實(shí)現(xiàn)某個(gè)領(lǐng)域的任務(wù),即做的也是領(lǐng)域內(nèi)的事情,是通用語(yǔ)言的表達(dá)。而不是應(yīng)用服務(wù),應(yīng)用服務(wù)是領(lǐng)域服務(wù)的客戶方,比如api聚合服務(wù),不應(yīng)該做領(lǐng)域內(nèi)的事情。也不是基礎(chǔ)設(shè)施服務(wù),比如DB或消息基礎(chǔ)組件。特別是不能跟現(xiàn)在常用的mvc+s架構(gòu)中的s(service)層混淆,這種情形下的s,很多時(shí)候是持久層接口組裝,更像是DDD中的資源庫(kù)的概念。
3、先考慮聚合或值對(duì)像的建模,不適合,然后才使用領(lǐng)域服務(wù)。聚合(實(shí)體)和值對(duì)像才是最重要的DDD建模對(duì)象,如果反而首先使用領(lǐng)域服務(wù),容易導(dǎo)致“貧血領(lǐng)域模型”。既然不適合直接在實(shí)體或值對(duì)像上建模,也基本說(shuō)明很多時(shí)候涉及到多個(gè)實(shí)體或值對(duì)像。
那什么時(shí)候該使用領(lǐng)域服務(wù)呢?
1.執(zhí)行一個(gè)顯著的業(yè)務(wù)操作過(guò)程
2.對(duì)領(lǐng)域?qū)ο筮M(jìn)行轉(zhuǎn)換
3.以多個(gè)領(lǐng)域?qū)ο笞鳛檩斎脒M(jìn)行計(jì)算,結(jié)果產(chǎn)生一個(gè)值對(duì)像基本就是跟上面對(duì)領(lǐng)域服務(wù)概念的理解是一致的。
領(lǐng)域服務(wù)實(shí)現(xiàn)是否需要獨(dú)立接口?
優(yōu)點(diǎn):使用接口表達(dá)領(lǐng)域概念,而技術(shù)實(shí)現(xiàn)可以比較隨意,比如放在基礎(chǔ)實(shí)施層,或者在依賴倒置原則中,放在應(yīng)用層實(shí)現(xiàn)也可以;獨(dú)立接口有利于解耦,通過(guò)依賴注入或工廠可以完全解耦客戶端與具體實(shí)現(xiàn);
缺點(diǎn):得寫兩個(gè)對(duì)象代碼,特別對(duì)于java,還得分兩個(gè)文件,閱讀代碼也增加點(diǎn)難度,而很多時(shí)候一個(gè)接口也只有一個(gè)實(shí)現(xiàn);另外一個(gè)命名問(wèn)題,在DDD中領(lǐng)域?qū)ο竺Q(對(duì)應(yīng)語(yǔ)言實(shí)現(xiàn)的類)和操作名稱(對(duì)應(yīng)函數(shù)名)是很重要的,是需要表達(dá)通用語(yǔ)言的概念的。但如果定義獨(dú)立接口,也就是會(huì)XXXservice的名字來(lái)定義接口,但服務(wù)實(shí)現(xiàn)用什么命名呢?如果用XXXserviceImpl,那其實(shí)也說(shuō)明可以不需要定義獨(dú)立接口了。測(cè)試領(lǐng)域服務(wù)其實(shí)測(cè)試方面,我覺得沒(méi)有很多需要關(guān)注的,或者說(shuō)我比較少測(cè)試方面的需要。但在測(cè)試領(lǐng)域服務(wù)一節(jié)有句話卻比較有意思“我們希望對(duì)領(lǐng)域服務(wù)進(jìn)行測(cè)試,并且希望從客戶端的角度對(duì)領(lǐng)域服務(wù)進(jìn)行建模,同時(shí)我們希望測(cè)試能夠反映查領(lǐng)域服務(wù)的使用方式”,即通過(guò)測(cè)試代碼,告訴客戶端怎么使用領(lǐng)域服務(wù)。這其實(shí)是測(cè)試代碼的一個(gè)重要的作用,但也經(jīng)常被我們忽略的。
注意:領(lǐng)域服務(wù)不能過(guò)多,那變成貧血模型了。
6 應(yīng)用服務(wù)層
應(yīng)用層通過(guò)應(yīng)用服務(wù)接口來(lái)暴露系統(tǒng)的全部功能。在應(yīng)用服務(wù)的實(shí)現(xiàn)中,它負(fù)責(zé)編排和轉(zhuǎn)發(fā),它將要實(shí)現(xiàn)的功能委托給一個(gè)或多個(gè)領(lǐng)域?qū)ο髞?lái)實(shí)現(xiàn),它本身只負(fù)責(zé)處理業(yè)務(wù)用例的執(zhí)行順序以及結(jié)果的拼裝。通過(guò)這樣一種方式,它隱藏了領(lǐng)域?qū)拥膹?fù)雜性及其內(nèi)部實(shí)現(xiàn)機(jī)制。
應(yīng)用層相對(duì)來(lái)說(shuō)是較“薄”的一層,除了定義應(yīng)用服務(wù)之外,在該層我們可以進(jìn)行安全認(rèn)證,權(quán)限校驗(yàn),持久化事務(wù)控制,或者向其他系統(tǒng)發(fā)生基于事件的消息通知,另外還可以用于創(chuàng)建郵件以發(fā)送給客戶等。
7 架構(gòu)及架構(gòu)風(fēng)格
一, 架構(gòu)部分:
1) 限界上下文、子域:
領(lǐng)域:通常是指整個(gè)系統(tǒng)的業(yè)務(wù)邊界.也可以指子域如核心域等。
子域:業(yè)務(wù)系統(tǒng)的某個(gè)方面。
限界上下文:同樣的是業(yè)務(wù)系統(tǒng)中某個(gè)方面,它比子域的粒度更小。通常一個(gè)子域可以只包含一個(gè)限界上下文。
光看這個(gè),有點(diǎn)繞。但直白點(diǎn)理解,其實(shí)它們就是架構(gòu)中的關(guān)于系統(tǒng)/模塊的劃分。系統(tǒng)可以劃分為多個(gè)子系統(tǒng),子系統(tǒng)當(dāng)然還可以劃分為更小的子系統(tǒng)。或者業(yè)務(wù)模塊的劃分。
如果還不夠直白,那么如果有微服務(wù)經(jīng)驗(yàn),可以想想、對(duì)照服務(wù)的劃分。
2)上下文映射圖:

DDD中的圖示。個(gè)人理解:其實(shí)它就是各子系統(tǒng)/模塊的依賴關(guān)系。比如現(xiàn)在典型電商系統(tǒng)子系統(tǒng)劃分 會(huì)員系統(tǒng)、商品管理系統(tǒng)、資金系統(tǒng)。。。

商品、資金均依賴于會(huì)員系統(tǒng)。基本上資金限界上下文同時(shí)也是一個(gè)子域。同時(shí)它們也各自被劃分為了一個(gè)微服務(wù)系統(tǒng)。
二,架構(gòu)風(fēng)格:
《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》關(guān)于架構(gòu)一章,章節(jié)名雖然叫架構(gòu),但應(yīng)該理解成架構(gòu)風(fēng)格。就象傳統(tǒng)的三層架一樣,是一種架構(gòu)風(fēng)格。
1)六邊形架構(gòu)

它并不是真的有六條邊.它也叫端口+適配器.端口可以理解成http,socket自定義傳輸協(xié)議、或者某個(gè)RPC調(diào)用協(xié)議等。六邊形的內(nèi)部代表了application和domain層。外部代表應(yīng)用的驅(qū)動(dòng)邏輯、基礎(chǔ)設(shè)施或其他應(yīng)用。內(nèi)部通過(guò)端口和外部系統(tǒng)通信,端口代表了一種協(xié)議,以API呈現(xiàn)。
一個(gè)例子:

2) Rest
RESTful風(fēng)格的架構(gòu)將‘資源’放在第一位,每個(gè)‘資源’都有一個(gè)URI與之對(duì)應(yīng),可以將‘資源’看著是ddd中的實(shí)體;RESTful采用具有自描述功能的消息實(shí)現(xiàn)無(wú)狀態(tài)通信,提高系統(tǒng)的可用性;至于‘資源’的哪些屬性可以公開出去,針對(duì)‘資源’的操作,RESTful使用HTTP協(xié)議的已有方法來(lái)實(shí)現(xiàn):GET、PUT、POST和DELETE。
3) CQRS
CQRS就是平常大家在講的讀寫分離,通常讀寫分離的目的是為了提高查詢性能,同時(shí)達(dá)到讀/寫的解耦。
CQRS適用于極少數(shù)復(fù)雜的業(yè)務(wù)領(lǐng)域,如果不是很適合反而會(huì)增加復(fù)雜度;另一個(gè)適用場(chǎng)景為獲取高性能的服務(wù)
4)事件驅(qū)動(dòng)架構(gòu)
事件可能有不同類型,這里主要只關(guān)心領(lǐng)域事件。

象不象微服務(wù)架構(gòu)中引入消息中間件來(lái)解耦和最終事務(wù)一致?
5)管道和過(guò)濾器風(fēng)格.
管道過(guò)濾器來(lái)源于linux命令類似 ps -ef | grep tomcat . | 即管道。ps 處理的結(jié)果經(jīng)過(guò)管道符| 作為下一個(gè)命令的輸入。
《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》中舉了個(gè)基于事件的發(fā)布、訂閱的例子來(lái)實(shí)現(xiàn)管道和過(guò)濾器架構(gòu)風(fēng)格。事件的發(fā)布訂閱中心作為管道。事件的發(fā)布、訂閱方作為過(guò)濾器。
推而廣之,考慮基于消息中間件的管道過(guò)濾器。
8 最后
1) 采用三層結(jié)構(gòu)的架構(gòu)風(fēng)格就沒(méi)有領(lǐng)域相關(guān)的類容了嗎?
答案當(dāng)然是有的,但是不象DDD這樣有明確的區(qū)分。往往因?yàn)槌绦騿T沒(méi)有相關(guān)概念或多多思考就容易引發(fā)問(wèn)題。舉個(gè)栗:
public class SearchController {
private SearchService service;
@RequestMapping(value="search")
public List search(String searchStr) {
service.search(searchStr);
}
}
public class SearchService{
public List search(String str){
if(系統(tǒng)變量=="solor"){
solorService.search(str)
}else {
dbService.search(str);
}
}
}
功能很強(qiáng)大,支持?jǐn)?shù)據(jù)檢索和solor檢索。但是再看,solor檢索和數(shù)據(jù)檢索明顯不是一個(gè)玩意,不應(yīng)該同時(shí)出現(xiàn)在SearchService里。從DDD觀點(diǎn)來(lái)看,也明顯屬于不同的領(lǐng)域?qū)崿F(xiàn)模型。即使在同一個(gè)子域里,劃分微服務(wù)那它也應(yīng)該是兩個(gè)微服務(wù)實(shí)現(xiàn)。顯明擴(kuò)展性不好。
2)DDD也有麻煩問(wèn)題
比如要考慮根實(shí)體、值對(duì)象、實(shí)體。都不象時(shí)考慮使用領(lǐng)域服務(wù)類。領(lǐng)域服務(wù)類干脆就是貧血模血。
3)最后的最后
作為六邊型架構(gòu)風(fēng)格的實(shí)現(xiàn),看看一個(gè)開發(fā)包的結(jié)構(gòu)圖
controller則是適配器。