聚合間的引用必須使用主鍵
第一條規(guī)則就是聚合間通過(guò)標(biāo)識(shí)(例如主鍵)來(lái)引用,而非通過(guò)對(duì)象引用。例如Order使用customerId引用其Customer,而非引用Customer對(duì)象。同樣,OrderLineItem使用productId引用Product。

該方法完全不同于傳統(tǒng)的對(duì)象建模。在對(duì)象建模中,領(lǐng)域模型中的外鍵被認(rèn)為是“設(shè)計(jì)異味”(譯者注:設(shè)計(jì)異味,Design Smell,指設(shè)計(jì)中違反了基本的設(shè)計(jì)原則并對(duì)設(shè)計(jì)的質(zhì)量產(chǎn)生了負(fù)面影響的特定結(jié)構(gòu))。使用標(biāo)識(shí)而非對(duì)象引用,這意味著聚合是松耦合的。這樣開(kāi)發(fā)人員易于將不同的聚合置于不同的服務(wù)中。事實(shí)上,一種服務(wù)的業(yè)務(wù)邏輯構(gòu)成一個(gè)領(lǐng)域模型,每個(gè)領(lǐng)域模型由一組聚合組成。例如,訂單服務(wù)包含了Order聚合,客戶服務(wù)包含了Customer聚合。
一個(gè)事務(wù)創(chuàng)建或更新一個(gè)聚合
聚合中必須要遵循的第二條規(guī)則是,一個(gè)事務(wù)只能去創(chuàng)建或更新一個(gè)聚合。我在很多年前第一次讀到該規(guī)則時(shí),感覺(jué)真是讓人無(wú)法理解!在那時(shí),我正在開(kāi)發(fā)傳統(tǒng)的基于RDBMS的整體應(yīng)用,事務(wù)可以去更新任意的數(shù)據(jù)。但時(shí)至今日,這條規(guī)則對(duì)于微服務(wù)架構(gòu)而言無(wú)疑是完美的。它確保了一個(gè)事務(wù)包含于一個(gè)服務(wù)之中。該規(guī)則也適合大多數(shù)NoSQL數(shù)據(jù)庫(kù)的受限事務(wù)。
在領(lǐng)域模型的開(kāi)發(fā)中,確定每個(gè)聚合的規(guī)模是開(kāi)發(fā)人員必須要做出的決策。從一個(gè)方面講,理想的聚合應(yīng)該是越小越好。這是因?yàn)榫酆贤ㄟ^(guò)分離問(wèn)題改進(jìn)了模塊化,也因?yàn)榫酆贤ǔJ峭耆虞d的,這樣細(xì)粒度的聚合就是更加高效的。此外,考慮到聚合的更新是順序發(fā)生的,使用細(xì)粒度聚合將增加應(yīng)用可處理的同步請(qǐng)求的個(gè)數(shù),進(jìn)而改進(jìn)了可擴(kuò)展性。再有,細(xì)粒度聚合降低了兩個(gè)用戶視圖更新同一聚合的可能性,改進(jìn)了用戶體驗(yàn)。從另一方面講,因?yàn)榫酆蠈儆谑聞?wù)的范疇,為保證特定更新操作的原子性,開(kāi)發(fā)人員可能需要定義更大規(guī)模的聚合。
在本文的前面,我以網(wǎng)店的領(lǐng)域模型為例介紹了如何將Order聚合和Customer聚合定義為不同的聚合。另一種設(shè)計(jì)方案是將所有的Order聚合作為Customer聚合的組成部分。由此得到的大型Customer聚合的優(yōu)點(diǎn)在于,應(yīng)用可原子性地強(qiáng)制實(shí)施信用檢查。但這種設(shè)計(jì)方案的缺點(diǎn)在于將訂單和客戶管理功能整合到了同一服務(wù)中。考慮到更新同一客戶不同訂單的事務(wù)將被序列化,該設(shè)計(jì)方案降低了應(yīng)用的可擴(kuò)展性。同樣的原理,在兩個(gè)用戶試圖去編輯同一客戶的不同訂單時(shí)可能會(huì)產(chǎn)生沖突。此外,加載Customer聚合的代價(jià)會(huì)隨訂單數(shù)量的增長(zhǎng)而日益增高。由于以上的原因,我們應(yīng)使聚合盡可能地細(xì)粒度。
盡管一個(gè)事務(wù)只能創(chuàng)建或更新一個(gè)聚合,應(yīng)用仍需去維持聚合間的一致性。例如訂單服務(wù)必須要驗(yàn)證一個(gè)新的Order聚合沒(méi)有超出Customer聚合的信用額度。維持一致性有兩種可選的做法。一種做法是在同一事務(wù)中對(duì)多個(gè)聚合進(jìn)行核查、創(chuàng)建或更新。這種方法僅適用于所有聚合歸屬于同一服務(wù)并在同一RDBMS中持久化的情況。另一種更正確的做法,是使用最終一致性且事件驅(qū)動(dòng)的方法去維持聚合間的一致性。
使用事件維持?jǐn)?shù)據(jù)一致性
在現(xiàn)代應(yīng)用中,在事務(wù)上有各種各樣的限制,這使得在服務(wù)間維持一致性是十分具有挑戰(zhàn)性的。每個(gè)服務(wù)具有其自身的數(shù)據(jù),這使得兩階段提交并非是一種可用的方法。此外,不少應(yīng)用使用了NoSQL數(shù)據(jù),而NoSQL數(shù)據(jù)庫(kù)并不支持本地ACID事務(wù),更不用說(shuō)分布事務(wù)了。因此,現(xiàn)代應(yīng)用必須使用一種事件驅(qū)動(dòng)的最終一致性事務(wù)模型。
什么是事件?
依據(jù) 韋氏詞典的定義 ,“事件”就是所發(fā)生的事情,在韋氏詞典中對(duì)事件定義的原文如下圖:

在本文中,我們將“領(lǐng)域事件”定義為在聚合上所發(fā)生的事情。事件通常表示了狀態(tài)的改變。例如,對(duì)于網(wǎng)店應(yīng)用中的Order聚合而言,它的狀態(tài)改變事件包括創(chuàng)建訂單、取消訂單、發(fā)送訂單等。事件表示了違反業(yè)務(wù)規(guī)則的嘗試,例如違反客戶的信用額度。
使用事件驅(qū)動(dòng)架構(gòu)
服務(wù)使用事件去維持聚合間的一致性,其做法是:在任何值得注意的情況發(fā)生時(shí),聚合就發(fā)布一個(gè)事件,這些情況可能是聚合的狀態(tài)改變,或是存在違反業(yè)務(wù)規(guī)則的可能性時(shí)。其它聚合訂閱事件,并通過(guò)更新自身狀態(tài)對(duì)事件進(jìn)行響應(yīng)。