領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)釋厄錄

基礎(chǔ)

學(xué)習(xí)DDD看的書(shū)

  1. 《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》
    基礎(chǔ)只是,概念
  2. 《領(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)分層

DDD所使用的傳統(tǒng)分層架構(gòu)
  • 一般我們使用聚合或者工廠對(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事件。
image.png

命令和查詢職責(zé)分離——CQRS

  1. 如果一個(gè)方法修改了對(duì)象的狀態(tài),該方法便是一個(gè)命令(Command),它不應(yīng)該返回?cái)?shù)據(jù)。在Java和C#中,這樣的方法應(yīng)該聲明為void。
  2. 如果一個(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ù)

  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ì)象。

領(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ī)則。

聚合的原則

  1. 在一致性邊界之內(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ù)一致性
  2. 設(shè)計(jì)小聚合
    因?yàn)榫酆闲枰WC在一個(gè)事務(wù)中只能修改一個(gè)聚合,所以聚合需要更加緊湊,邏輯更加一致,保證在單個(gè)聚合的事務(wù)一致性,多個(gè)聚合直接,需要實(shí)現(xiàn)最終一致性。
  3. 通過(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也有好處。
  4. 在邊界之外使用最終一致性
    即多個(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ù)
  1. 面向集合的資源庫(kù)

面向集合的資源庫(kù)是指資源庫(kù)就像一個(gè)集合,在向一個(gè)Set集合中添加聚合時(shí),如果重復(fù)添加是不會(huì)成功的,從集合中獲取到的實(shí)例也可以直接進(jìn)行修改而不需要寫(xiě)會(huì)操作,因?yàn)閺募现蝎@取到的對(duì)象是引用類型,這意味著任何修改都會(huì)直接生效。類似Hibernate,但是用的不多

  1. 面向持久化的資源庫(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
  1. 使用FACTORY(工廠)來(lái)創(chuàng)建和重建復(fù)雜對(duì)象和AGGREGATE(聚合),從而封裝它們的內(nèi)部結(jié)構(gòu)。
  2. 在生命周期的中間和末尾使用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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容