使用了DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))后释牺,代碼編寫(xiě)有什么不一樣呢萝衩?這可能是程序員們?cè)诮佑|DDD后最關(guān)心的一個(gè)問(wèn)題。這個(gè)系列文章會(huì)對(duì)一些優(yōu)秀的DDD實(shí)例代碼進(jìn)行分析没咙,管中窺豹猩谊,略見(jiàn)數(shù)斑。這是第二篇祭刚。
上一篇文章里通過(guò)分析IDDD_Sample中的領(lǐng)域?qū)哟a占比(90%)和單元測(cè)試代覆蓋率(90%)來(lái)從外部視角感受了富領(lǐng)域模型和我們平時(shí)常用的貧血模型的不同牌捷,感受了富領(lǐng)域模型帶來(lái)的一些好處。
但是上一篇文章結(jié)尾也建議先不要急于實(shí)踐富領(lǐng)域模型涡驮,因?yàn)檫@也是需要具備一些前提條件的暗甥。在了解實(shí)現(xiàn)富領(lǐng)域模型需要什么樣的前提條件之前,我們需要深入地區(qū)了解它捉捅,看看它是什么樣子的撤防,有什么特點(diǎn)。
本篇文章我們會(huì)深入到IDDD_Sample代碼的內(nèi)部棒口,看看富領(lǐng)域模型的代碼是怎么寫(xiě)的寄月,同時(shí)也會(huì)感受到富領(lǐng)域模型帶來(lái)的更多的好處。
人人喜歡白富美
當(dāng)你剛接手一個(gè)已有應(yīng)用時(shí)无牵,了解完這個(gè)應(yīng)用這整個(gè)系統(tǒng)架構(gòu)中所處的位置和所承擔(dān)的職責(zé)后漾肮,就需要去了解這個(gè)應(yīng)用的內(nèi)部實(shí)現(xiàn)。通常是如何入手的呢茎毁?
- 看文檔
- 問(wèn)交接給你的人
- 翻代碼/Debug
這三個(gè)方式一個(gè)比一個(gè)靠譜一些初橘。
- 文檔可能十分稀少,即使有一些也可能是充滿了謬誤充岛,只能半信半疑;
- 交接的人好不容易把這爛攤子扔給你耕蝉,棄如敝履崔梗,恨不得永不再碰它,哪有耐心給你講明白垒在,而且可能他也很難講明白蒜魄。如同你新交了一個(gè)女朋友,你希望她的上一任男友把她的一切給你講明白一樣场躯;
- 那只能自己弄明白谈为,只能一點(diǎn)點(diǎn)去翻代碼,去探索真相踢关。這是唯一靠譜的方法伞鲫,然而這個(gè)過(guò)程很是痛苦,熟悉剛接手的代碼签舞,和熟悉剛接手的女朋友一樣的難秕脓,且漫長(zhǎng)柒瓣;
但你去閱讀 IDDD_Sample的代碼就不會(huì)這樣,因?yàn)樗軉渭兎图埽龥](méi)有心機(jī)芙贫,她毫不隱藏,她胸懷坦露傍药,因?yàn)樗且粋€(gè)“白富美”磺平。
- “白” - 坦白,從代碼就能看出模型拐辽,不隱藏小秘密拣挪;
- “富” - 富領(lǐng)域模型,不但擁有屬性薛训,還擁有很多方法媒吗,具有獨(dú)立的人格和行為能力,你很容易知道她有什么和能干什么乙埃;她的社會(huì)關(guān)系也很豐富闸英;
- “美” - 清晰簡(jiǎn)單就是一種大美,結(jié)構(gòu)之美和邏輯之美介袜;
這樣的女朋友代碼誰(shuí)不喜歡呢甫何?
揭開(kāi)面紗看真容
我們將 com.saasovation.agilepm.domain.model.product.backlogitem
這個(gè)包下面的代碼生成領(lǐng)域模型圖:

我們能獲得以下的這些領(lǐng)域知識(shí):
- 我們有三個(gè)實(shí)體(BacklogItem,Task遇伞, EstimationLogEntry)
- 還有四個(gè)值對(duì)象辙喂,比如看到 “BusinessPriority”,就能理解“業(yè)務(wù)優(yōu)先級(jí)”這個(gè)概念
- 有四個(gè)枚舉鸠珠,比如“StoryPoints” 巍耗,可以知道“故事估算的點(diǎn)數(shù)”這個(gè)概念(0,1渐排,2异雁,3锁蠕,5,8……)
目前,相對(duì)于貧血模型還沒(méi)有太多的優(yōu)勢(shì)拌禾,除了多出的那幾個(gè)值對(duì)象鬓照,能直接顯示一些業(yè)務(wù)概念外弃甥。
讓我們?cè)趫D上展示更多的元素惶室,顯示實(shí)體和值對(duì)象以及枚舉間的關(guān)系。

這時(shí)候我們會(huì)看到帘靡,BacklogItem是一個(gè)聚合根知给,BacklogItem,Task测柠, EstimationLogEntry等是它的子對(duì)象炼鞠,而且一個(gè)BacklogItem下可以有N個(gè)Task……
關(guān)于聚合關(guān)系的定義缘滥,可以通過(guò)UML來(lái)了解
在貧血模型的代碼實(shí)現(xiàn)里,要出現(xiàn)這個(gè)圖是不是需要根據(jù)數(shù)據(jù)庫(kù)外鍵來(lái)推斷谒主,在腦子里畫(huà)出來(lái)呢朝扼?或者維護(hù)在一個(gè)文檔里呢?而對(duì)于富領(lǐng)域模型的代碼霎肯,這個(gè)圖是通過(guò)工具隨時(shí)生成的擎颖。
代碼變化了,圖就會(huì)變化观游,所以這是一個(gè)“活”文檔搂捧。
如果你想了解更多,你可以在圖上展示領(lǐng)域?qū)ο蟮膶傩院头椒ǘ啤A私夥椒ㄒ簿褪撬男袨楹苤匾逝埽悴荒苤豢此撵o態(tài)屬性,比如白皮膚大眼睛搪柑,還要看它睡覺(jué)打不打呼聋丝,流不流哈喇子。

比如BacklogItem里有一個(gè)方法 commitTo(Sprint aSprint)
工碾,從名字上就能看出來(lái)弱睦,一個(gè)BacklogItem是可以提交到一個(gè)Sprint里的。下面是方法的實(shí)現(xiàn)細(xì)節(jié):
public void commitTo(Sprint aSprint) {
//省略各種校驗(yàn)……
if (this.isCommittedToSprint()) {
if (!aSprint.sprintId().equals(this.sprintId())) {
this.uncommitFromSprint();
}
}
this.elevateStatusWith(BacklogItemStatus.COMMITTED);
this.setSprintId(aSprint.sprintId());
DomainEventPublisher
.instance()
.publish(new BacklogItemCommitted(
this.tenantId(),
this.backlogItemId(),
this.sprintId()));
}
前面忘記說(shuō)了渊额,領(lǐng)域模型的代碼里有很多領(lǐng)域事件(比如 BacklogItemCommitted
)况木,領(lǐng)域事件也能幫助我們非常好地理解領(lǐng)域知識(shí)。
白富美能當(dāng)飯吃嗎
白富美固然好旬迹,但能當(dāng)飯吃嗎火惊?在領(lǐng)域模型里寫(xiě)了一堆內(nèi)存操作的邏輯,這樣的代碼能有用嗎奔垦?
嗯矗晃,這個(gè)問(wèn)題暴露了我們的階層,我們向往白富美宴倍,又怕養(yǎng)不起,相對(duì)于對(duì)美好生活的向往仓技,我們更擔(dān)心的是餓肚子鸵贬。
放心,能吃飽脖捻,管夠阔逼。下次我們?cè)僬f(shuō)。