基礎(chǔ)
學(xué)習(xí)DDD看的書(shū)
- 《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》
基礎(chǔ)只是,概念- 《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對(duì)之道 》
使用,流程,串聯(lián)
千萬(wàn)不要先看 《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對(duì)之道 》,不然你會(huì)哭的,因?yàn)槟憧吹暮吞鞎?shū)基本沒(méi)啥區(qū)別,要先看 《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》,然后在去看 埃文斯的那本書(shū)。
領(lǐng)域模型
領(lǐng)域模型是某個(gè)業(yè)務(wù)領(lǐng)域的軟件模型,通常通過(guò)對(duì)象模型來(lái)實(shí)現(xiàn)。
貧血模型和充血模型
- 在貧血模型中,對(duì)象中只有屬性值和公有的get set 方法,幾乎沒(méi)有業(yè)務(wù)邏輯,完全就是一個(gè)數(shù)據(jù)集。
- 在充血模型中,數(shù)據(jù)與對(duì)應(yīng)的業(yè)務(wù)邏輯被封裝在一個(gè)類中,這種模型滿足面向?qū)ο蟮姆庋b特性,是典型的面向?qū)ο缶幊獭?/li>
- 充血模型和貧血模型是領(lǐng)域?qū)ο蠛头穷I(lǐng)域?qū)ο蟮闹匾獏^(qū)別。
失憶癥
- 當(dāng)業(yè)務(wù)邏輯出現(xiàn)多次變化的適合,直接散落在項(xiàng)目中的各個(gè)業(yè)務(wù)邏輯很可能忘記其本來(lái)的意圖,而業(yè)務(wù)的迭代,可能會(huì)導(dǎo)致項(xiàng)目越來(lái)越復(fù)雜,這樣就會(huì)出現(xiàn),除作者以外,甚至作者,看這段代碼的適合會(huì)蒙圈,這種一般稱為貧血模型導(dǎo)致的失憶癥。
是充血模型的好處
- 復(fù)用程度高,表達(dá)能力強(qiáng),適合復(fù)雜的業(yè)務(wù)邏輯處理
- 業(yè)務(wù)邏輯的變動(dòng),很可能會(huì)導(dǎo)致領(lǐng)域模型的變動(dòng),但是,這個(gè)在傳統(tǒng)的開(kāi)發(fā)中也是不可避免的,但是充血模型將邏輯高內(nèi)聚,反而不容易漏改邏輯
舉例 -- 這個(gè)可以不看
一個(gè)簡(jiǎn)單的主子訂單模型,加上付款操作
在傳統(tǒng)模式下,可見(jiàn)我們的對(duì)象只是一個(gè)數(shù)據(jù)集合,除了傳遞數(shù)據(jù),并沒(méi)有做任何操作,這個(gè)只是一個(gè)簡(jiǎn)單的付款,如果之后,業(yè)務(wù)場(chǎng)景復(fù)雜后,很可能出現(xiàn),這個(gè)一個(gè)業(yè)務(wù)邏輯,哪里一個(gè)業(yè)務(wù)邏輯
public class Order {
private Long orderId;
private String orderNo;
private Long amount;
private Integer status;
---get set 方法 ---
}
public class OrderItem {
private Long id;
private Long orderId;
private Long orderNo;
private String itemName;
private Long payAmount;
--- get set 方法---
}
付款方法
public void pay(OrderReq orderReq){
Order order = dao1.query(orderReq);
List<OrderItem> orderItems = dao2.query(orderReq);
---業(yè)務(wù)邏輯
order.setStatus(PAY);
for (OrderItem orderItem : orderItems) {
----業(yè)務(wù)邏輯
orderItem.setPayAmount(orderReq.getPayAmount());
}
transaction.execute(){
dao1.save(order);
dao2.save(orderItems);
}
}
業(yè)務(wù)更新后,很可能
public void pay(OrderReq orderReq){
Order order = dao1.query(orderReq);
List<OrderItem> orderItems = dao2.query(orderReq);
---業(yè)務(wù)邏輯
order.setStatus(PAY);
if(會(huì)員){
order.setStatus(PAY1);
}
if(優(yōu)惠){
order.setStatus(PAY2);
}
for (OrderItem orderItem : orderItems) {
----業(yè)務(wù)邏輯
if(優(yōu)惠){
orderItem.setStatus(PAY2);
}
if(會(huì)員){
orderItem.setStatus(PAY1);
}
orderItem.setPayAmount(orderReq.getPayAmount());
}
transaction.execute(){
dao1.save(order);
dao2.save(orderItems);
}
}
領(lǐng)域是什么
在實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)一書(shū)中這樣解釋
從廣義上講,領(lǐng)域(Domain)即是一個(gè)組織所做的事情以及其中所包含的一切。商業(yè)機(jī)構(gòu)通常會(huì)確定一個(gè)市場(chǎng),然后在這個(gè)市場(chǎng)中銷售產(chǎn)品和服務(wù)。每個(gè)組織都有它自己的業(yè)務(wù)范圍和做事方式。這個(gè)業(yè)務(wù)范圍以及在其中所進(jìn)行的活動(dòng)便是領(lǐng)域。當(dāng)你為某個(gè)組織開(kāi)發(fā)軟件時(shí),你面對(duì)的便是這個(gè)組織的領(lǐng)域。這個(gè)領(lǐng)域?qū)τ谀銇?lái)說(shuō)應(yīng)該是明晰的,因?yàn)槟阍谶@個(gè)領(lǐng)域中工作。
在我看來(lái),領(lǐng)域其實(shí)就是我們的業(yè)務(wù)模塊,或者說(shuō)是我們的實(shí)際業(yè)務(wù)就是領(lǐng)域。
子域
對(duì)一個(gè)大的領(lǐng)域的問(wèn)題進(jìn)行根據(jù)業(yè)務(wù)職能的劃分,例如在一個(gè)電商項(xiàng)目中,商品,訂單,支付,履約,結(jié)算,庫(kù)存,物流,發(fā)票,用戶就是不同的子域。而在子域的劃分中,也會(huì)產(chǎn)生不同的類型,核心子域,通用子域,支撐子域等等。
限界上下文
- 特定模型的限界應(yīng)用。限界上下文使團(tuán)隊(duì)所有成員能夠明確地知道什么必須保持一致,什么必須獨(dú)立開(kāi)發(fā)。
- 限界上下文為業(yè)務(wù)流程在一個(gè)劃定的界限,由一個(gè)或者多個(gè)子域組成。
- 在基于某一個(gè)角度,將多個(gè)子域劃分進(jìn)一個(gè)限界上下文
- 限界上下文即是一個(gè)特定的解決方案,在一個(gè)特定的限界上下文只使用一套通用語(yǔ)言,并且保證它的清晰性和簡(jiǎn)潔性。
問(wèn)題空間和解決問(wèn)題空間
- 問(wèn)題空間:是我們業(yè)務(wù)所遇到的挑戰(zhàn),問(wèn)題空間是領(lǐng)域的一部分,對(duì)問(wèn)題空間的開(kāi)發(fā)將產(chǎn)生一個(gè)新的核心域,問(wèn)題空間是核心域和其他子域的組合。
- 解決問(wèn)題空間:如何實(shí)現(xiàn)軟件以解決這些業(yè)務(wù)挑戰(zhàn),包括一個(gè)或多個(gè)限界上下文,即一組特定的軟件模型來(lái)解決問(wèn)題。
上下文映射圖
-
表示限界上下文和他們呢的集成關(guān)系,下面就是一個(gè)上下文映射圖,
U表示上游(Upstream),D表示下游(Downstream)
領(lǐng)域驅(qū)動(dòng)架構(gòu)
系統(tǒng)分層

- 一般我們使用聚合或者工廠對(duì)復(fù)雜的聚合進(jìn)行創(chuàng)建,而不是直接在應(yīng)用層創(chuàng)建
- 領(lǐng)域模型用于發(fā)布領(lǐng)域事件時(shí),應(yīng)用層可以將訂閱方注冊(cè)到任意數(shù)量的事件上,這樣的好處是可以對(duì)事件進(jìn)行存儲(chǔ)和轉(zhuǎn)發(fā)。同時(shí),領(lǐng)域模型只需要關(guān)注自己的核心邏輯;領(lǐng)域事件發(fā)布器也可以保持輕量化,而不用依賴于消息機(jī)制的基礎(chǔ)設(shè)施,可以使用Spring的event事件。

命令和查詢職責(zé)分離——CQRS
- 如果一個(gè)方法修改了對(duì)象的狀態(tài),該方法便是一個(gè)命令(Command),它不應(yīng)該返回?cái)?shù)據(jù)。在Java和C#中,這樣的方法應(yīng)該聲明為void。
- 如果一個(gè)方法返回了數(shù)據(jù),該方法便是一個(gè)查詢(Query),此時(shí)它不應(yīng)該通過(guò)直接的或間接的手段修改對(duì)象的狀態(tài)。在Java和C#中,這樣的方法應(yīng)該以其返回的數(shù)據(jù)類型進(jìn)行聲明。
防腐層
- 當(dāng)你的領(lǐng)域和其他領(lǐng)域存在數(shù)據(jù)交互時(shí),你需要一個(gè)防腐層作為兩個(gè)領(lǐng)域之間的紐帶。這會(huì)給你帶來(lái)很多“數(shù)據(jù)轉(zhuǎn)換”的代碼,但是對(duì)于業(yè)務(wù)多變的系統(tǒng)來(lái)講,它能保護(hù)你的領(lǐng)域。
- 在我們的微服務(wù)架構(gòu)中,一般來(lái)說(shuō)指當(dāng)前服務(wù)與其他服務(wù)進(jìn)行交互的適合,并不直接調(diào)用,而由一個(gè)中間層,來(lái)進(jìn)行判斷,處理,緩沖。
實(shí)體
- 一種對(duì)象,它不是由屬性來(lái)定義的,而是通過(guò)一連串的連續(xù)事件和標(biāo)識(shí)定義的。
- 實(shí)體應(yīng)該具有唯一標(biāo)示,如訂單號(hào),身份證號(hào),用戶id
- 唯一標(biāo)示可以有用戶產(chǎn)生(如手機(jī)號(hào))/系統(tǒng)產(chǎn)生(如uuid)/持久化機(jī)制生成(如mysql主鍵)
- 實(shí)體的其他信息可以進(jìn)行修改,但是無(wú)論如何修改,唯一標(biāo)示是不變的,其還是一個(gè)相同的實(shí)體。
值對(duì)象
- 一種描述了某種特征或?qū)傩缘珱](méi)有概念標(biāo)識(shí)的對(duì)象。
- 度量或描述,它度量或者描述了領(lǐng)域中的一件東西。
- 不變性,可以作為不變量,一個(gè)值對(duì)象創(chuàng)建了就不能改變了
-
概念整體,它將不同的相關(guān)的屬性組合成一個(gè)概念整體。一個(gè)值對(duì)象可以只處理單個(gè)屬性,也可以處理一組相關(guān)聯(lián)的屬性。在這組相關(guān)聯(lián)的屬性中,每一個(gè)屬性都是整體屬性所不可或缺的組成部分,這和簡(jiǎn)單地將一組屬性組裝在對(duì)象中是不同的。如果一組屬性聯(lián)合起來(lái)并不能表達(dá)一個(gè)整體上的概念,那么這種聯(lián)合并無(wú)多大用處。
即他可以是單個(gè)屬性,也可以是多個(gè)屬性,但必須是有關(guān)聯(lián)性的,而不是一堆屬性的聚合。 - 可替換性,當(dāng)度量和描述改變時(shí),可以用另一個(gè)值對(duì)象予以替換。
- 值對(duì)象相等性,它可以和其他值對(duì)象進(jìn)行相等性比較
- 無(wú)副作用行為,它不會(huì)對(duì)協(xié)作對(duì)象造成副作用
最小化集成
在所有的DDD項(xiàng)目中,通常存在多個(gè)限界上下文,這意味著我們需要找到合適的方法對(duì)這些上下文進(jìn)行集成。當(dāng)模型概念從上游上下文流入下游上下文中時(shí),盡量使用值對(duì)象來(lái)表示這些概念。這樣的好處是可以達(dá)到最小化集成,即可以最小化下游模型中用于管理職責(zé)的屬性數(shù)目。使用不變的值對(duì)象使得我們做更少的職責(zé)假設(shè)。
這里我理解的最小化集成,就是我們的限界上下文直接的參數(shù)應(yīng)該是值對(duì)象
領(lǐng)域服務(wù)
- 領(lǐng)域中的服務(wù)表示一個(gè)無(wú)狀態(tài)的操作,它用于實(shí)現(xiàn)特定于某個(gè)領(lǐng)域的任務(wù)
- 慎重使用領(lǐng)域服務(wù),能在聚合和實(shí)體中完成的操作,不要放在領(lǐng)域服務(wù)中,過(guò)度使用領(lǐng)域服務(wù)將會(huì)造成貧血領(lǐng)域模型。
- 領(lǐng)域服務(wù)通常和領(lǐng)域內(nèi)相關(guān)的聚合放在同一模塊(目錄)下。
什么是領(lǐng)域服務(wù)
- 執(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ì)象。
領(lǐng)域事件 Domain Event
- 領(lǐng)域事件用于發(fā)布和捕捉發(fā)生在領(lǐng)域內(nèi)的一些事情,領(lǐng)域事件通常是由某個(gè)聚合發(fā)出,在整個(gè)領(lǐng)域范圍內(nèi)訂閱。某某方法執(zhí)行完成、某某數(shù)據(jù)修改成功、某某接口調(diào)用結(jié)束等等都可以理解為領(lǐng)域內(nèi)事件,這些事件結(jié)束后我們往往需要做一些進(jìn)一步的操作,比如:訂單領(lǐng)域完成下單操作后,物流領(lǐng)域需要開(kāi)始物流。這是在兩個(gè)領(lǐng)域之間具備先后關(guān)系的事件,問(wèn)題在于完成下單之后怎么通知物流領(lǐng)域呢?直接調(diào)用嗎?領(lǐng)域事件認(rèn)為,在有需要時(shí),當(dāng)訂單創(chuàng)建完成,應(yīng)當(dāng)對(duì)外發(fā)布一個(gè)攜帶了必要對(duì)象信息的訂單創(chuàng)建完成事件(OrderCreatedEvent)消息,由訂閱方訂閱到該事件消息,進(jìn)而完成后續(xù)業(yè)務(wù)。
- 領(lǐng)域事件是對(duì)領(lǐng)域內(nèi)發(fā)生的活動(dòng)進(jìn)行的建模
- 我覺(jué)的領(lǐng)域事件就使用事務(wù)消息就比較好,別的書(shū)里說(shuō)的感覺(jué)用處不大
模塊
- 可以理解為 package分包
- 模塊表示了一個(gè)命名的容器,用于存放領(lǐng)域中內(nèi)聚在一起的類。
- 將類放在不同模塊中的目的在于達(dá)到松耦合性。
- 由于DDD中的模塊并不是一個(gè)通用的存儲(chǔ)區(qū)域,因此對(duì)其進(jìn)行適當(dāng)?shù)拿侵匾摹?/li>
- 事實(shí)上,模塊名是通用語(yǔ)言的重要組成部分。
- 模塊應(yīng)該包含一組具有高內(nèi)聚性的概念集合,這樣做的好處是可以在不同的模塊之間實(shí)現(xiàn)松耦合
模塊的命名規(guī)范
- 通常模塊名就是包名,一般來(lái)說(shuō)是公司層面統(tǒng)一的
- 先考慮模塊,再是限界上下文,因?yàn)槟K的目的在于組織那些內(nèi)聚在一起的領(lǐng)域?qū)ο?/li>
聚合
- 將實(shí)體和值對(duì)象在一致性邊界之內(nèi)組成聚合(Aggregate)
- 聚合就是一組相關(guān)對(duì)象的集合,我們把聚合作為數(shù)據(jù)修改的單元。外部對(duì)象只能引用聚合中的一個(gè)成員,我們把它稱為根。在聚合的邊界之內(nèi)應(yīng)用一組一致的規(guī)則。
聚合的原則
- 在一致性邊界之內(nèi)建模真正的不變條件:
不變條件是指某個(gè)業(yè)務(wù)規(guī)則,這個(gè)規(guī)則應(yīng)該總是保持一致的。這里的一致通常是指事務(wù)一致性,因?yàn)榫酆献鳛槲覀儾僮骱妥x取數(shù)據(jù)的基本單元,這就要求我們?cè)谔峤皇聞?wù)時(shí),該聚合邊界內(nèi)的所有對(duì)象都應(yīng)當(dāng)保持一致。 即在一個(gè)事務(wù)中只修改一個(gè)聚合實(shí)例,需要保證事務(wù)一致性 - 設(shè)計(jì)小聚合
因?yàn)榫酆闲枰WC在一個(gè)事務(wù)中只能修改一個(gè)聚合,所以聚合需要更加緊湊,邏輯更加一致,保證在單個(gè)聚合的事務(wù)一致性,多個(gè)聚合直接,需要實(shí)現(xiàn)最終一致性。 - 通過(guò)唯一標(biāo)識(shí)引用其他聚合
即聚合直接引用的適合使用唯一標(biāo)示引用,而不是直接引用聚合對(duì)象。在java中,通俗標(biāo)示,就是我們因?yàn)榱硪粋€(gè)聚合的唯一id,而不是直接引用對(duì)象,這樣我們的聚合會(huì)更加輕量級(jí),減少內(nèi)存,對(duì)與java來(lái)說(shuō),對(duì)gc也有好處。 - 在邊界之外使用最終一致性
即多個(gè)聚合直接需要保持最終一致性,一般使用事件驅(qū)動(dòng)來(lái)完成最終一致性
工廠 Factory
- 將創(chuàng)建復(fù)雜對(duì)象和聚合的職責(zé)分配給一個(gè)單獨(dú)的對(duì)象,該對(duì)象本身并不承擔(dān)領(lǐng)域模型中的職責(zé),但是依然是領(lǐng)域設(shè)計(jì)的一部分。工廠應(yīng)該提供一個(gè)創(chuàng)建對(duì)象的接口,該接口封裝了所有創(chuàng)建對(duì)象的復(fù)雜操作過(guò)程,同時(shí),它并不需要客戶去引用那個(gè)實(shí)際被創(chuàng)建的對(duì)象。對(duì)于聚合來(lái)說(shuō),我們應(yīng)該一次性地創(chuàng)建整個(gè)聚合,并且確保它的不變條件得到滿足
- 隱藏創(chuàng)建細(xì)節(jié),方便調(diào)用
資源庫(kù) Repository
- 我理解的在我們的開(kāi)發(fā)過(guò)程中,資源庫(kù)就是我們的dao層,與數(shù)據(jù)存儲(chǔ)進(jìn)行交互的。一般資源庫(kù)分為面向集合的資源庫(kù)和面向持久化的資源庫(kù)
- 面向集合的資源庫(kù)
面向集合的資源庫(kù)是指資源庫(kù)就像一個(gè)集合,在向一個(gè)Set集合中添加聚合時(shí),如果重復(fù)添加是不會(huì)成功的,從集合中獲取到的實(shí)例也可以直接進(jìn)行修改而不需要寫(xiě)會(huì)操作,因?yàn)閺募现蝎@取到的對(duì)象是引用類型,這意味著任何修改都會(huì)直接生效。類似Hibernate,但是用的不多
- 面向持久化的資源庫(kù)
面向持久化的資源庫(kù)就是我們熟悉的數(shù)據(jù)庫(kù)操作,但是我們需要把實(shí)現(xiàn)細(xì)節(jié)進(jìn)行封裝,資源庫(kù)應(yīng)該盡可能地模擬一個(gè)集合的操作方式,將細(xì)節(jié)封裝,例如防止聚合添加重復(fù)的校驗(yàn)不再由客戶端做,調(diào)用方不關(guān)心細(xì)節(jié),可以完全作為一個(gè)集合的方式使用。
還有
- 對(duì)事務(wù)的管理絕對(duì)不應(yīng)該放在領(lǐng)域模型和領(lǐng)域?qū)又?,而?yīng)該放在應(yīng)用層
資源庫(kù)和dao的區(qū)別
- DAO主要從數(shù)據(jù)庫(kù)表的角度來(lái)看待問(wèn)題,并且提供CRUD操作,DAO相關(guān)的模式通常只是對(duì)數(shù)據(jù)庫(kù)表的一層封裝。
- 而資源庫(kù)采用面向集合的方式,而不是面向數(shù)據(jù)訪問(wèn)的方式。
- 所以 資源庫(kù) 可以看作 DAO,但是DAO不一定是資源庫(kù)
基礎(chǔ)設(shè)施
基礎(chǔ)設(shè)施的職責(zé)是為應(yīng)用程序的其他部分提供技術(shù)支持。
集成限界上下文
- 常見(jiàn)的手段包括開(kāi)放領(lǐng)域服務(wù)接口、開(kāi)放HTTP服務(wù)以及消息發(fā)布-訂閱機(jī)制。
領(lǐng)域?qū)ο蟮纳芷?/h2>
image.png
- 使用FACTORY(工廠)來(lái)創(chuàng)建和重建復(fù)雜對(duì)象和AGGREGATE(聚合),從而封裝它們的內(nèi)部結(jié)構(gòu)。
- 在生命周期的中間和末尾使用REPOSITORY(存儲(chǔ)庫(kù))來(lái)提供查找和檢索持久化對(duì)象并封裝龐大基礎(chǔ)設(shè)施的手段。
https://zhuanlan.zhihu.com/p/350033901
https://tech.meituan.com/2017/12/22/ddd-in-practice.html

https://zhuanlan.zhihu.com/p/350033901
https://tech.meituan.com/2017/12/22/ddd-in-practice.html
