欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)之事件驅(qū)動(dòng)與CQRS

 更新時(shí)間:2021年06月17日 17:24:14   作者:vivo互聯(lián)網(wǎng)技術(shù)  
這篇文章分析了如何應(yīng)用事件來分離軟件核心復(fù)雜度。探究CQRS為什么廣泛應(yīng)用于DDD項(xiàng)目中,以及如何落地實(shí)現(xiàn)CQRS框架。當(dāng)然我們也要警惕一些失敗的教訓(xùn),利弊分析以后再去抉擇正確的應(yīng)對(duì)之道

一、前言:從物流詳情開始

大家對(duì)物流跟蹤都不陌生,它詳細(xì)記錄了在什么時(shí)間發(fā)生了什么,并且數(shù)據(jù)作為重要憑證是不可變的。我理解其背后的價(jià)值有這么幾個(gè)方面:業(yè)務(wù)方可以管控每個(gè)子過程、知道目前所處的環(huán)節(jié);另一方面,當(dāng)需要追溯時(shí)候僅僅通過每一步的記錄就可以回放整個(gè)歷史過程。

我在之前的文章中提出過“軟件項(xiàng)目也是人類社會(huì)生產(chǎn)關(guān)系的范疇,只不過我們所創(chuàng)造的勞動(dòng)成果看不見摸不著而已”。所以我們可以借鑒物流跟蹤的思路來開發(fā)軟件項(xiàng)目,把復(fù)雜過程拆解為一個(gè)個(gè)步驟、子過程、狀態(tài),這和我們事件劃分是一致的,這就是事件驅(qū)動(dòng)的典型案例。

二、領(lǐng)域事件

領(lǐng)域事件(Domain Events)是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain Driven Design,DDD)中的一個(gè)概念,用于捕獲我們所建模的領(lǐng)域中所發(fā)生過的事情。

領(lǐng)域事件本身也作為通用語(yǔ)言(Ubiquitous Language)的一部分成為包括領(lǐng)域?qū)<以趦?nèi)的所有項(xiàng)目成員的交流用語(yǔ)。

比如在前述的跨境物流例子中,貨品達(dá)到保稅倉(cāng)以后需要分派工作人員進(jìn)行分揀分包,那么“貨品已到達(dá)保稅倉(cāng)”便是一個(gè)領(lǐng)域事件。

首先,從業(yè)務(wù)邏輯來說該事件關(guān)系到整個(gè)流程的成功或者失?。煌瑫r(shí)又將觸發(fā)后續(xù)子流程;而對(duì)于業(yè)務(wù)方來說,該事件也是一個(gè)標(biāo)志性的里程碑,代表自己的貨品就快配送到自己手中。

所以通常來說,一個(gè)領(lǐng)域事件具有以下幾個(gè)特征:較高的業(yè)務(wù)價(jià)值,有助于形成完整的業(yè)務(wù)閉環(huán),將導(dǎo)致進(jìn)一步的業(yè)務(wù)操作。這里還要強(qiáng)調(diào)一點(diǎn),領(lǐng)域事件具有明確的邊界。

比如:如果你建模的是餐廳的結(jié)賬系統(tǒng),那么此時(shí)的“客戶已到達(dá)”便不是你關(guān)心的重點(diǎn),因?yàn)槟悴豢赡茉诳蛻舻竭_(dá)時(shí)就立即向?qū)Ψ揭X,而“客戶已下單”才是對(duì)結(jié)賬系統(tǒng)有用的事件。

2.1、建模領(lǐng)域事件

在建模領(lǐng)域事件時(shí),我們應(yīng)該根據(jù)限界上下文中的通用語(yǔ)言來命名事件及屬性。如果事件由聚合上的命令操作產(chǎn)生,那么我們通常根據(jù)該操作方法的名字來命名領(lǐng)域事件。

對(duì)于上面的例子“貨品已到達(dá)保稅倉(cāng)”,我們將發(fā)布與之對(duì)應(yīng)的領(lǐng)域事件

GoodsArrivedBondedWarehouseEvent(當(dāng)然在明確的界限上下文中也可以去掉聚合的名字,直接建模為ArrivedBondedWarehouseEvent,這都是命名方面的習(xí)慣)。

事件的名字表明了聚合上的命令方法在執(zhí)行成功之后所發(fā)生的事情,換句話說待定項(xiàng)以及不確定的狀態(tài)是不能作為領(lǐng)域事件的。

一個(gè)行之有效的方法是畫出當(dāng)前業(yè)務(wù)的狀態(tài)流轉(zhuǎn)圖,包含前置操作以及引起的狀態(tài)變更,這里表達(dá)的是已經(jīng)變更完成的狀態(tài)所以我們不用過去時(shí)態(tài)表示,比如刪除或者取消,即代表已經(jīng)刪除或者已經(jīng)取消。

然后對(duì)于其中的節(jié)點(diǎn)進(jìn)行事件建模。如下圖是文件云端存儲(chǔ)的業(yè)務(wù),我們分別對(duì)預(yù)上傳、上傳完成確認(rèn)、刪除等環(huán)節(jié)建?!斑^去時(shí)”事件,PreUploadedEvent、ConfirmUploadedEvent、RemovedEvent。

2.2、領(lǐng)域事件代碼解讀

package domain.event;

import java.util.Date;
import java.util.UUID;

public class DomainEvent {

    /**
     * 領(lǐng)域事件還包含了唯一ID,
     * 但是該ID并不是實(shí)體(Entity)層面的ID概念,
     * 而是主要用于事件追溯和日志。
     * 如果是數(shù)據(jù)庫(kù)存儲(chǔ),該字段通常為唯一索引。
     */
    private final String id;

    /**
     * 創(chuàng)建時(shí)間用于追溯,另一方面不管使用了
     * 哪種事件存儲(chǔ)都有可能遇到事件延遲,
     * 我們通過創(chuàng)建時(shí)間能夠確保其發(fā)生順序。
     */
    private final Date occurredOn;

    public DomainEvent() {
        this.id = String.valueOf(UUID.randomUUID());
        this.occurredOn = new Date();
    }
}

在創(chuàng)建領(lǐng)域事件時(shí),需要注意2點(diǎn):

  • 領(lǐng)域事件本身應(yīng)該是不變的(Immutable);
  • 領(lǐng)域事件應(yīng)該攜帶與事件發(fā)生時(shí)相關(guān)的上下文數(shù)據(jù)信息,但是并不是整個(gè)聚合根的狀態(tài)數(shù)據(jù)。例如,在創(chuàng)建訂單時(shí)可以攜帶訂單的基本信息,而對(duì)于用戶更新訂單收貨地址事件AddressUpdatedEvent事件,只需要包含訂單、用戶以及新的地址等信息即可。
public class AddressUpdatedEvent extends DomainEvent {
    //通過userId+orderId來校驗(yàn)訂單的合法性;
    private String userId; 
    private String orderId;
    //新的地址
    private Address address;
    //略去具體業(yè)務(wù)邏輯
}

2.3、領(lǐng)域事件的存儲(chǔ)

事件的不可變性與可追溯性都決定了其必須要持久化的原則,我們來看看常見的幾種方案。

2.3.1、單獨(dú)的EventStore

有的業(yè)務(wù)場(chǎng)景中會(huì)創(chuàng)建一個(gè)單獨(dú)的事件存儲(chǔ)中心,可能是Mysql、Redis、Mongo、甚至文件存儲(chǔ)等。這里以Mysql舉例,business_code、event_code用來區(qū)分不同業(yè)務(wù)的不同事件,具體的命名規(guī)則可以根據(jù)實(shí)際需要。

這里需要注意該數(shù)據(jù)源與業(yè)務(wù)數(shù)據(jù)源不一致的場(chǎng)景,我們要確保當(dāng)業(yè)務(wù)數(shù)據(jù)更新以后事件能夠準(zhǔn)確無(wú)誤的記錄下來,實(shí)踐中盡量避免使用分布式事務(wù),或者盡量避免其跨庫(kù)的場(chǎng)景,否則你就得想想如何補(bǔ)償了。千萬(wàn)要避免,用戶更新了收貨地址,但是AddressUpdatedEvent事件保存失敗。

總的原則就是對(duì)分布式事務(wù)Say No,無(wú)論如何,我相信方法總比問題多,在實(shí)踐中我們總可以想到解決方案,區(qū)別在于該方案是否簡(jiǎn)潔、是否做到了解耦。

# 考慮是否需要分表,事件存儲(chǔ)建議邏輯簡(jiǎn)單
CREATE TABLE `event_store` (
  `event_id` int(11) NOT NULL auto increment,
  `event_code` varchar(32) NOT NULL,
  `event_name` varchar(64) NOT NULL,
  `event_body` varchar(4096) NOT NULL,
  `occurred_on` datetime NOT NULL,
  `business_code` varchar(128) NOT NULL,
  UNIQUE KEY (`event id`)
) ENGINE=InnoDB COMMENT '事件存儲(chǔ)表';

2.3.2、與業(yè)務(wù)數(shù)據(jù)一起存儲(chǔ)

在分布式架構(gòu)中,每個(gè)模塊都做的相對(duì)比較小,準(zhǔn)確的說是“自治”。如果當(dāng)前業(yè)務(wù)數(shù)據(jù)量較小,可以將事件與業(yè)務(wù)數(shù)據(jù)一起存儲(chǔ),用相關(guān)標(biāo)識(shí)區(qū)分是真實(shí)的業(yè)務(wù)數(shù)據(jù)還是事件記錄;或者在當(dāng)前業(yè)務(wù)數(shù)據(jù)庫(kù)中建立該業(yè)務(wù)自己的事件存儲(chǔ),但是要考慮到事件存儲(chǔ)的量級(jí)必然大于真實(shí)的業(yè)務(wù)數(shù)據(jù),考慮是否需要分表。

這種方案的優(yōu)勢(shì):數(shù)據(jù)自治;避免分布式事務(wù);不需要額外的事件存儲(chǔ)中心。當(dāng)然其劣勢(shì)就是不能復(fù)用。

2.4、領(lǐng)域事件如何發(fā)布

2.4.1、由領(lǐng)域聚合發(fā)送領(lǐng)域事件

/*
* 一個(gè)關(guān)于比賽的充血模型例子
* 貧血模型會(huì)構(gòu)造一個(gè)MatchService,我們這里通過模型來觸發(fā)相應(yīng)的事件
* 本例中略去了具體的業(yè)務(wù)細(xì)節(jié)
*/
public class Match {
    public void start() {
        //構(gòu)造Event....
        MatchEvent matchStartedEvent = new MatchStartedEvent();
        //略去具體業(yè)務(wù)邏輯
        DefaultDomainEventBus.publish(matchStartedEvent);
    }

    public void finish() {
        //構(gòu)造Event....
        MatchEvent matchFinishedEvent = new MatchFinishedEvent();
        //略去具體業(yè)務(wù)邏輯
        DefaultDomainEventBus.publish(matchFinishedEvent);
    }

    //略去Match對(duì)象基本屬性
}

2.4.2、事件總線VS消息中間件

微服務(wù)內(nèi)的領(lǐng)域事件可以通過事件總線或利用應(yīng)用服務(wù)實(shí)現(xiàn)不同聚合之間的業(yè)務(wù)協(xié)同。即微服務(wù)內(nèi)發(fā)生領(lǐng)域事件時(shí),由于大部分事件的集成發(fā)生在同一個(gè)線程內(nèi),不一定需要引入消息中間件。但一個(gè)事件如果同時(shí)更新多個(gè)聚合數(shù)據(jù),按照 DDD“一個(gè)事務(wù)只更新一個(gè)聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對(duì)微服務(wù)內(nèi)不同的聚合根采用不同的事務(wù)

三、Saga分布式事務(wù)

3.1、Saga概要

我們看看如何使用 Saga 模式維護(hù)數(shù)據(jù)一致性?

Saga 是一種在微服務(wù)架構(gòu)中維護(hù)數(shù)據(jù)一致性的機(jī)制,它可以避免分布式事務(wù)所帶來的問題。

一個(gè) Saga 表示需要更新的多個(gè)服務(wù)中的一個(gè),即Saga由一連串的本地事務(wù)組成。每一個(gè)本地事務(wù)負(fù)責(zé)更新它所在服務(wù)的私有數(shù)據(jù)庫(kù),這些操作仍舊依賴于我們所熟悉的ACID事務(wù)框架和函數(shù)庫(kù)。

模式:Saga

通過使用異步消息來協(xié)調(diào)一系列本地事務(wù),從而維護(hù)多個(gè)服務(wù)之間的數(shù)據(jù)一致性。

請(qǐng)參閱(強(qiáng)烈建議):https://microservices.io/patterns/data/saga.html

Saga與TCC相比少了一步Try的操作,TCC無(wú)論最終事務(wù)成功失敗都需要與事務(wù)參與方交互兩次。而Saga在事務(wù)成功的情況下只需要與事務(wù)參與方交互一次, 如果事務(wù)失敗,需要額外進(jìn)行補(bǔ)償回滾。

  • 每個(gè)Saga由一系列sub-transaction Ti 組成;
  • 每個(gè)Ti 都有對(duì)應(yīng)的補(bǔ)償動(dòng)作Ci,補(bǔ)償動(dòng)作用于撤銷Ti造成的結(jié)果;

可以看到,和TCC相比,Saga沒有“預(yù)留”動(dòng)作,它的Ti就是直接提交到庫(kù)。

Saga的執(zhí)行順序有兩種:

  • success:T1, T2, T3, ..., Tn ;
  • failure:T1, T2, ..., Tj, Cj,..., C2, C1,其中0 < j < n;

所以我們可以看到Saga的撤銷十分關(guān)鍵,可以說使用Saga的難點(diǎn)就在于如何設(shè)計(jì)你的回滾策略。

3.2、Saga實(shí)現(xiàn)

通過上面的例子我們對(duì)Saga有了初步的體感,現(xiàn)在來深入探討下如何實(shí)現(xiàn)。當(dāng)通過系統(tǒng)命令啟動(dòng)Saga時(shí),協(xié)調(diào)邏輯必須選擇并通知第一個(gè)Saga參與方執(zhí)行本地事務(wù)。一旦該事務(wù)完成,Saga協(xié)調(diào)選擇并調(diào)用下一個(gè)Saga參與方。

這個(gè)過程一直持續(xù)到Saga執(zhí)行完所有步驟。如果任何本地事務(wù)失敗,則 Saga必須以相反的順序執(zhí)行補(bǔ)償事務(wù)。以下幾種不同的方法可用來構(gòu)建Saga的協(xié)調(diào)邏輯。

3.2.1、協(xié)同式(choreography)

把 Saga 的決策和執(zhí)行順序邏輯分布在 Saga的每一個(gè)參與方中,它們通過交換事件的方式來進(jìn)行溝通。

(引用于《微服務(wù)架構(gòu)設(shè)計(jì)模式》相關(guān)章節(jié))

Order服務(wù)創(chuàng)建一個(gè)Order并發(fā)布OrderCreated事件。

Consumer服務(wù)消費(fèi)OrderCreated事件,驗(yàn)證消費(fèi)者是否可以下訂單,并發(fā)布ConsumerVerified事件。

Kitchen服務(wù)消費(fèi)OrderCreated事件,驗(yàn)證訂單,在CREATE_PENDING狀態(tài)下創(chuàng)建故障單,并發(fā)布TicketCreated事件。

Accounting服務(wù)消費(fèi)OrderCreated事件并創(chuàng)建一個(gè)處于PENDING狀態(tài)的Credit CardAuthorization。

Accounting服務(wù)消費(fèi)TicketCreated和ConsumerVerified事件,向消費(fèi)者的信用卡收費(fèi),并發(fā)布信用卡授權(quán)失敗事件。

Kitchen服務(wù)使用信用卡授權(quán)失敗事件并將故障單的狀態(tài)更改為REJECTED。

訂單服務(wù)消費(fèi)信用卡授權(quán)失敗事件,并將訂單狀態(tài)更改為已拒絕。

3.2.2、編排式(orchestration)

把Saga的決策和執(zhí)行順序邏輯集中在一個(gè)Saga編排器類中。Saga 編排器發(fā)出命令式消息給各個(gè) Saga 參與方,指示這些參與方服務(wù)完成具體操作(本地事務(wù))。類似于一個(gè)狀態(tài)機(jī),當(dāng)參與方服務(wù)完成操作以后會(huì)給編排器發(fā)送一個(gè)狀態(tài)指令,以決定下一步做什么。

(引用于《微服務(wù)架構(gòu)設(shè)計(jì)模式》相關(guān)章節(jié))

我們來分析一下執(zhí)行流程

Order Service首先創(chuàng)建一個(gè)Order和一個(gè)創(chuàng)建訂單控制器。之后,路徑的流程如下:

Saga orchestrator向Consumer Service發(fā)送Verify Consumer命令。

Consumer Service回復(fù)Consumer Verified消息。

Saga orchestrator向Kitchen Service發(fā)送Create Ticket命令。

Kitchen Service回復(fù)Ticket Created消息。

Saga協(xié)調(diào)器向Accounting Service發(fā)送授權(quán)卡消息。

Accounting服務(wù)部門使用卡片授權(quán)消息回復(fù)。

Saga orchestrator向Kitchen Service發(fā)送Approve Ticket命令。

Saga orchestrator向訂單服務(wù)發(fā)送批準(zhǔn)訂單命令。

3.2.3、補(bǔ)償策略

之前的描述中我們說過Saga最重要的是如何處理異常,狀態(tài)機(jī)還定義了許多異常狀態(tài)。如上面的6就會(huì)發(fā)生失敗,觸發(fā)AuthorizeCardFailure,此時(shí)我們就要結(jié)束訂單并把之前提交的事務(wù)進(jìn)行回滾。這里面要區(qū)分哪些是校驗(yàn)性事務(wù)、哪些是需要補(bǔ)償?shù)氖聞?wù)。

一個(gè)Saga由三種不同類型的事務(wù)組成:可補(bǔ)償性事務(wù)(可以回滾,因此有一個(gè)補(bǔ)償事務(wù));關(guān)鍵性事務(wù)(這是 Saga的成敗關(guān)鍵點(diǎn),比如4賬戶代扣);以及可重復(fù)性事務(wù),它不需要回滾并保證能夠完成(比如6更新狀態(tài))。

在Create Order Saga 中,createOrder()、createTicket()步驟是可補(bǔ)償性事務(wù)且具有撤銷其更新的補(bǔ)償事務(wù)。

verifyConsumerDetails()事務(wù)是只讀的,因此不需要補(bǔ)償事務(wù)。authorizeCreditCard()事務(wù)是這個(gè) Saga的關(guān)鍵性事務(wù)。如果消費(fèi)者的信用卡可以授權(quán),那么這個(gè)Saga保證完成。approveTicket()和approveRestaurantOrder()步驟是在關(guān)鍵性事務(wù)之后的可重復(fù)性事務(wù)。

認(rèn)真拆解每個(gè)步驟、然后評(píng)估其補(bǔ)償策略尤為重要,正如你看到的,每種類型的事務(wù)在對(duì)策中扮演著不同的角色。

四、CQRS

前面講述了事件的概念,又分析了Saga如何解決復(fù)雜事務(wù),現(xiàn)在我們來看看CQRS為什么在DDD中廣泛被采用。除了讀寫分離的特征以外,我們用事件驅(qū)動(dòng)的方式來實(shí)踐Command邏輯能有效降低業(yè)務(wù)的復(fù)雜度。

當(dāng)你明白如何建模事件、如何規(guī)避復(fù)雜事務(wù),明白什么時(shí)候用消息中間件、什么時(shí)候采用事件總線,才能理解為什么是CQRS、怎么正確應(yīng)用。

下面是我們項(xiàng)目中的設(shè)計(jì),這里為什么會(huì)出現(xiàn)Read/Write Service,是為了封裝調(diào)用,service內(nèi)部是基于聚合發(fā)送事件。因?yàn)槲野l(fā)現(xiàn)在實(shí)際項(xiàng)目中,很多人都會(huì)第一時(shí)間問我要XXXService而不是XXX模型,所以在DDD沒有完全普及的項(xiàng)目中建議大家采取這種居中策略。這也符合咱們的解耦,對(duì)方依賴我的抽象能力,然而我內(nèi)部是基于DDD還是傳統(tǒng)的流程代碼對(duì)其是無(wú)關(guān)透明的。

我們先來看看事件以及處理器的時(shí)序關(guān)系。

這里還是以文件云端存儲(chǔ)業(yè)務(wù)為例,下面是一些處理器的核心代碼。注釋行是對(duì)代碼功能、用法以及擴(kuò)展方面的解讀,請(qǐng)認(rèn)真閱讀。

package domain;

import domain.event.DomainEvent;
import domain.handler.event.DomainEventHandler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DomainRegistry {

    private Map<String, List<DomainEventHandler>> handlerMap =
        new HashMap<String, List<DomainEventHandler>>();

    private static DomainRegistry instance;

    private DomainRegistry() {
    }

    public static DomainRegistry getInstance() {
        if (instance == null) {
            instance = new DomainRegistry();
        }
        return instance;
    }

    public Map<String, List<DomainEventHandler>> getHandlerMap() {
        return handlerMap;
    }

    public List<DomainEventHandler> find(String name) {
        if (name == null) {
            return null;
        }
        return handlerMap.get(name);
    }

    //事件注冊(cè)與維護(hù),register分多少個(gè)場(chǎng)景根據(jù)業(yè)務(wù)拆分,
    //這里是業(yè)務(wù)流的核心。如果多個(gè)事件需要維護(hù)前后依賴關(guān)系,
    //可以維護(hù)一個(gè)priority邏輯
    public void register(Class<? extends DomainEvent> domainEvent,
                         DomainEventHandler handler) {
        if (domainEvent == null) {
            return;
        }
        if (handlerMap.get(domainEvent.getName()) == null) {
            handlerMap.put(domainEvent.getName(), new ArrayList<DomainEventHandler>());
        }
        handlerMap.get(domainEvent.getName()).add(handler);
        //按照優(yōu)先級(jí)進(jìn)行事件處理器排序
        。。。
    }
}

文件上傳完畢事件的例子。

package domain.handler.event;

import domain.DomainRegistry;
import domain.StateDispatcher;
import domain.entity.meta.MetaActionEnums;
import domain.event.DomainEvent;
import domain.event.MetaEvent;
import domain.repository.meta.MetaRepository;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

/**
 * @Description:一個(gè)事件操作的處理器
 * 我們混合使用了Saga的兩種模式,外層事件交互;
 * 對(duì)于單個(gè)復(fù)雜的事件內(nèi)部采取狀態(tài)流轉(zhuǎn)實(shí)現(xiàn)。
 */

@Component
public class MetaConfirmUploadedHandler implements DomainEventHandler {

    @Resource
    private MetaRepository metaRepository;

    public void handle(DomainEvent event) {
        //1.我們?cè)诋?dāng)前的上下文中定義個(gè)ThreadLocal變量
        //用于存放事件影響的聚合根信息(線程共享)

        //2.當(dāng)然如果有需要額外的信息,可以基于event所
        //攜帶的信息構(gòu)造Specification從repository獲取
        // 代碼示例
        // metaRepository.queryBySpecification(SpecificationFactory.build(event));

        DomainEvent domainEvent = metaRepository.load();

        //此處是我們的邏輯
        。。。。

        //對(duì)于單個(gè)操作比較復(fù)雜的,可以使用狀態(tài)流轉(zhuǎn)進(jìn)一步拆分
        domainEvent.setStatus(nextState);
        //在事件觸發(fā)之后,仍需要一個(gè)狀態(tài)跟蹤器來解決大事務(wù)問題
        //Saga編排式
        StateDispatcher.dispatch();
    }

    @PostConstruct
    public void autoRegister() {
        //此處可以更加細(xì)分,注冊(cè)在哪一類場(chǎng)景中,這也是事件驅(qū)動(dòng)的強(qiáng)大、靈活之處。
        //避免了if...else判斷。我們可以有這樣的意識(shí),一旦你的邏輯里面充斥了大量
        //switch、if的時(shí)候來看看自己注冊(cè)的場(chǎng)景是否可以繼續(xù)細(xì)分
        DomainRegistry.getInstance().register(MetaEvent.class, this);
    }

    public String getAction() {
        return MetaActionEnums.CONFIRM_UPLOADED.name();
    }

    //適用于前后依賴的事件,通過優(yōu)先級(jí)指定執(zhí)行順序
    public Integer getPriority() {
        return PriorityEnums.FIRST.getValue();
    }
}

事件總線邏輯

package domain;

import domain.event.DomainEvent;
import domain.handler.event.DomainEventHandler;
import java.util.List;


public class DefaultDomainEventBus {

    public static void publish(DomainEvent event, String action,
                               EventCallback callback) {

        List<DomainEventHandler> handlers = DomainRegistry.getInstance().
            find(event.getClass().getName());
        handlers.stream().forEach(handler -> {
            if (action != null && action.equals(handler.getAction())) {
                Exception e = null;
                boolean result = true;
                try {
                    handler.handle(event);
                } catch (Exception ex) {
                    e = ex;
                    result = false;
                    //自定義異常處理
                    。。。
                } finally {
                    //write into event store
                    saveEvent(event);
                }

                //根據(jù)實(shí)際業(yè)務(wù)處理回調(diào)場(chǎng)景,DefaultEventCallback可以返回
                if (callback != null) {
                    callback.callback(event, action, result, e);       
                }
            }
        });
    }
}

五、自治服務(wù)和系統(tǒng)

DDD中強(qiáng)調(diào)限界上下文的自治特性,事實(shí)上,從更小的粒度來看,對(duì)象仍然需要具備自治的這四個(gè)特性,即:最小完備、自我履行、穩(wěn)定空間、獨(dú)立進(jìn)化。其中自我履行是重點(diǎn),因?yàn)椴粡?qiáng)依賴外部所以穩(wěn)定、因?yàn)榉€(wěn)定才可能獨(dú)立進(jìn)化。這就是六邊形架構(gòu)在DDD中較為普遍的原因。

六、結(jié)語(yǔ)

本文所講述的事件、Saga、CQRS的方案均可以單獨(dú)使用,可以應(yīng)用到你的某個(gè)method、或者你的整個(gè)package。項(xiàng)目中我們并不一定要實(shí)踐一整套CQRS,只要其中的某些思想解決了我們項(xiàng)目中的某個(gè)問題就足夠了。

也許你現(xiàn)在已經(jīng)磨刀霍霍,準(zhǔn)備在項(xiàng)目中實(shí)踐一下這些技巧。不過我們要明白“每一個(gè)硬幣都有兩面性”,我們不僅看到高擴(kuò)展、解耦的、易編排的優(yōu)點(diǎn)以外,仍然要明白其所帶來的問題。利弊分析以后再去決定如何實(shí)現(xiàn)才是正確的應(yīng)對(duì)之道。

  • 這類編程模式有一定的學(xué)習(xí)曲線;
  • 基于消息傳遞的應(yīng)用程序的復(fù)雜性;
  • 處理事件的演化有一定難度;
  • 刪除數(shù)據(jù)存在一定難度;
  • 查詢事件存儲(chǔ)庫(kù)非常有挑戰(zhàn)性。

不過我們還是要認(rèn)識(shí)到在其適合的場(chǎng)景中,六邊形架構(gòu)以及DDD戰(zhàn)術(shù)將加速我們的領(lǐng)域建模過程,也迫使我們從嚴(yán)格的通用語(yǔ)言角度來解釋一個(gè)領(lǐng)域,而不是一個(gè)個(gè)需求。任何更強(qiáng)調(diào)核心域而不是技術(shù)實(shí)現(xiàn)的方式都可以增加業(yè)務(wù)價(jià)值,并使我們獲得更大的競(jìng)爭(zhēng)優(yōu)勢(shì)。

以上就是詳解領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)之事件驅(qū)動(dòng)與CQRS的詳細(xì)內(nèi)容,更多關(guān)于領(lǐng)域驅(qū)動(dòng)設(shè)計(jì) 事件驅(qū)動(dòng)與CQRS的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring的定時(shí)任務(wù)@Scheduled源碼詳解

    Spring的定時(shí)任務(wù)@Scheduled源碼詳解

    這篇文章主要介紹了Spring的定時(shí)任務(wù)@Scheduled源碼詳解,@Scheduled注解是包org.springframework.scheduling.annotation中的一個(gè)注解,主要是用來開啟定時(shí)任務(wù),本文提供了部分實(shí)現(xiàn)代碼與思路,需要的朋友可以參考下
    2023-09-09
  • Spring Cache相關(guān)知識(shí)總結(jié)

    Spring Cache相關(guān)知識(shí)總結(jié)

    今天帶大家學(xué)習(xí)Spring的相關(guān)知識(shí),文中對(duì)Spring Cache作了非常詳細(xì)的介紹,對(duì)正在學(xué)習(xí)Java Spring的小伙伴們很有幫助,需要的朋友可以參考下
    2021-05-05
  • jpa多數(shù)據(jù)源時(shí)Hibernate配置自動(dòng)生成表不生效的解決

    jpa多數(shù)據(jù)源時(shí)Hibernate配置自動(dòng)生成表不生效的解決

    這篇文章主要介紹了jpa多數(shù)據(jù)源時(shí)Hibernate配置自動(dòng)生成表不生效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java實(shí)現(xiàn)的DES加密解密工具類實(shí)例

    Java實(shí)現(xiàn)的DES加密解密工具類實(shí)例

    這篇文章主要介紹了Java實(shí)現(xiàn)的DES加密解密工具類,結(jié)合具體實(shí)例形式分析了Java實(shí)現(xiàn)的DES加密解密工具類定義與使用方法,需要的朋友可以參考下
    2017-09-09
  • SpringCloud LoadBalancer自定義負(fù)載均衡器使用解析

    SpringCloud LoadBalancer自定義負(fù)載均衡器使用解析

    LoadBalancerClient 是 SpringCloud 提供的一種負(fù)載均衡客戶端,Ribbon 負(fù)載均衡組件內(nèi)部也是集成了 LoadBalancerClient 來實(shí)現(xiàn)負(fù)載均衡,本文給大家深入解析 LoadBalancerClient 接口源碼,感興趣的朋友跟隨小編一起看看吧
    2023-04-04
  • JAVA抽象類,接口,內(nèi)部類詳解

    JAVA抽象類,接口,內(nèi)部類詳解

    這篇文章主要給大家介紹了關(guān)于Java中抽象類,接口,內(nèi)部類的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-09-09
  • RestTemplate設(shè)置超時(shí)時(shí)間及返回狀態(tài)碼非200處理

    RestTemplate設(shè)置超時(shí)時(shí)間及返回狀態(tài)碼非200處理

    這篇文章主要為大家介紹了RestTemplate設(shè)置超時(shí)時(shí)間及返回狀態(tài)碼非200處理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • SpringBoot單機(jī)限流的實(shí)現(xiàn)

    SpringBoot單機(jī)限流的實(shí)現(xiàn)

    在系統(tǒng)運(yùn)維中, 有時(shí)候?yàn)榱吮苊庥脩舻膼阂馑⒔涌? 會(huì)加入一定規(guī)則的限流,本文主要介紹了SpringBoot單機(jī)限流的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-08-08
  • GC調(diào)優(yōu)實(shí)戰(zhàn)之高分配速率High?Allocation?Rate

    GC調(diào)優(yōu)實(shí)戰(zhàn)之高分配速率High?Allocation?Rate

    這篇文章主要為大家介紹了GC調(diào)優(yōu)之高分配速率High?Allocation?Rate的實(shí)戰(zhàn)示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2022-01-01
  • Java獲取時(shí)間差(天數(shù)差,小時(shí)差,分鐘差)代碼示例

    Java獲取時(shí)間差(天數(shù)差,小時(shí)差,分鐘差)代碼示例

    這篇文章主要介紹了Java獲取時(shí)間差(天數(shù)差,小時(shí)差,分鐘差)代碼示例,使用SimpleDateFormat來實(shí)現(xiàn)的相關(guān)代碼,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11

最新評(píng)論