從壹開始微服務 [ DDD ] 之十二 ║ 核心篇【下】:事件驅動EDA 詳解


緣起

哈嘍大家好,又是周二了,時間很快,我的第二個系列DDD領域驅動設計講解已經接近尾聲了,除了今天的時間驅動EDA(也有可能是兩篇),然后就是下一篇的事件回溯,就剩下最后的權限驗證了,然后就完結了,這兩個月我也是一直在自學,然后再想栗子,個人感覺收獲還是很大的,比如DDD領域分層設計、CQRS讀寫分離、CommandBus命令總線、EDA事件驅動、四色原理等等,如果大家真的能踏踏實實的看完,或者說多看看書,對個人的思想提高有很大的幫助,這里要說兩點,可能會有一些小伙伴不開心,但是還是要說說:

1、很多小伙伴一直問我看什么書,我個人感覺,只要是書看就對了,與其糾結哪本,還不如踏踏實實先看一本。

2、還有小伙伴問,為啥還沒有看到微服務的內容?

我想說,其實微服務是一個很寬泛的領域,比如.net core的深入學習,依賴注入的使用,倉儲契約、DDD+事件總線的學習、中介者模式、Docker的學習、容器化設計等等等等,這些都屬於微服務的范疇,如果這些基礎知識不會的話,可能是學不好微服務的。

周末的時候,我又好好的整理了下我的Github上的代碼,然后新建了一些分支(如果你不會使用Git命令,可以看我的一個文章:https://www.jianshu.com/p/2b666a08a3b5,會一直更新),主要是這樣的(這個數字是對應的文章,比如今天的是第 12 ):

其實我這個系列所說的 DDD領域驅動設計,是一個很豐富的概念,里邊包含了DDD的多層設計思想、CQRS、Bus、EDA、ES等等,所以如果你只想要其中的一部分,可以對應的分支進行Clone,比如你單純想要一個干凈的基於DDD四層設計的模板,可以克隆 Framework_DDD_8 這個分支,如果你想帶有讀寫分離,可以克隆 CQRS_DDD_9 這個分支等等,也方便好好研究。

關於CQRS讀寫分離概念,請注意,分離不一定是分庫,一個數據庫也能實現讀寫分離,最簡單的就是從Code上來區分。

 

前言

好啦,上邊說了一些周末的思考,現在馬上進入正文,不知道大家對上周的內容還有沒有印象,主要用兩篇文章來說明了命令總線的設計思想和執行過程《十 ║領域驅動【實戰篇·中】:命令總線Bus分發(一)》、《十一 ║ 基於源碼分析,命令分發的過程(二)》,咱們很好的實現了多個復雜模型間的解耦,成功的簡化了API接口層和 Application應用服務層,把重心真正的轉義到了領域層。

當然其中也有一些新的問題出現了,這個也可以當作今天的每篇一問:

首先,對領域通知的處理上,目前用的是通過一個 ErrorData 的key 來把錯誤通知放到了內存里,然后去讀取,這樣有一個很危險的問題,就是生命周期的問題,如果在當前實例中,沒有及時刪除,可能會出現錯誤通知的混亂,這是致命的,當然還有 key 的問題,因為幾乎每一個 Command 都會有不同的信息,我們不能通過簡簡單單的人為取名字來實現這個邏輯,這是荒唐的。

其次,如果我們 Command 執行完成,是如何發布通知的,比如注冊成功的郵件,短信分發,站內推送等等。

最后,不知道大家有沒有深入的去學習,去了解 MediatR 中介者的兩個模式:請求/響應模式 與 發布/訂閱模式的區別和聯系(詳細的下邊會說到)。

 你會說,很簡單呀,我們直接在 CommandHandler 命令處理程序中處理不就行了,一步一步往下走就可以了呀,如果你現在還有這樣的思維,那DDD可真的好好再學習了,為什么呢?很簡單,我們當時為什么要把 contrller 的業務邏輯剝離到領域模型,就是為了業務獨立化,不讓多個不相干的業務纏繞(比如我們之前是把model 驗證、錯誤返回、發郵件等,都是寫在 controller 里的),那如果我們再把過多的業務邏輯寫到命令處理程序中的話,那命令處理模型不就成為了第二個 controller 了么?我們為業務把 controller 剝離了一次,那今天咱們就繼續從 命令處理程序中,再優化一次。

 

零、今天要實現右下角藍色的部分

 

(周末有一個小伙伴問這個軟件的地址:https://www.mindmeister.com,應該需要翻牆)

 

一、領域事件驅動設計 —— EDA

1、什么是領域事件 

我們先看看官網,在《實現領域驅動設計》一書中對領域事件的定義如下:

領域專家所關心的發生在領域中的一些事件。

將領域中所發生的活動建模成一系列的離散事件。

每個事件都用領域對象來表示,領域事件是領域模型的組成部分,表示領域中所發生的事情。

領域事件:Domain Event,是針對某個業務來說的,或者說針對某個聚合的業務來說的,例如訂單生成這種業務,它可以同時對應一種事件,比如叫做OrderGeneratorEvent,而你的零散業務可能隨時會變,加一些業務,減一些業務,而對於訂單生成這個事件來說,它是唯一不變的,而我們需要把這些由產生訂單而發生變化的事情拿出來,而拿出來的這些業務就叫做"領域事件".其中的領域指的就是訂單生成這個聚合;而事件指的就是那些零散業務的統稱.

 

2、領域事件包含了哪些內容

如果你對上一篇命令總線很熟悉,這里就特別簡單,幾乎是一個模式,只不過總線發布的方式不一樣罷了,如果你比較熟悉命令驅動,這里正好溫習。如果不了解,這里就一起看吧,千萬記得再回去看前兩篇內容喲。

在面向對象的編程世界里,做這種事情我們需要幾個抽象:

領域對象事件標示:標示接口,接口的一種,用來約束一批對象,IEvent(當前也可以使用抽象類,本文即是)

領域對象的處理方法行為:比如 StudentEventHandler。(我們的命令處理程序也是如此)

事件總線:事件處理核心類,承載了事件的發布,訂閱與取消訂閱的邏輯,EventBus(這個和我們的命令總線CommandBus很類似)

某個領域對象的事件:它是一個事件處理類,它實現了 EventHandler,它所處理的事情需要在Handle里去完成

一個領域事件可以理解為是發生在一個特定領域中的事件,是你希望在同一個領域中其他部分知道並產生后續動作的事件。一個領域事件必須對業務有價值,有助於形成完整的業務閉環,也即一個領域事件將導致進一步的業務操作。就比如我們今天說到的領域通知,就應該是一個事件,我們從命令中產生的錯誤提示,通過處理程序,引發到事件總線內,並返回到前台。

 

3、為什么需要領域事件

領域事件也是一種基於事件的架構(EDA)。事件架構的好處可以把處理的流程解耦,實現系統可擴展性,提高主業務流程的內聚性。

在咱們文章的開頭,可說到了這個問題,不知道大家是否還記得,咱們再分析一下:

我們提交了一個添加Student 的申請,系統在完成保存后,可能還需要發送一個通知(當然這里錯誤信息,也有成功的),當然肯定還會會一些其他的后台服務的活動。如果把這一系列的動作放入一個處理過程中,會產生幾個的明顯問題:

1、一個是命令提交的的事務比較長,性能會有問題,甚至在極端情況下容易引發數據庫的嚴重故障(服務器方面);

2、另外提交的服務內聚性差,可維護性差,在業務流程發生變更時候,需要頻繁修改主程序(程序員方面)。

3、我們有時候只關心核心的流程,就比如添加Student,我們只關心是否添加成功,而且我們需要對這個成功有反饋,但是發郵件的功能,我們卻不用放在主業務中,甚至發送成功與否,不影響 Student 的正常添加,這樣我們就把后續的這些活動事件,從主業務中剝離開,實現了高內聚和低耦合(業務方面)。

還記得 MediatR 有兩個中介者模式么:請求/響應 和 發布/訂閱。在我們的系統中,添加一個學生命令,就是用到的請求/響應 IRequest 模式,因為我們需要等待當前操作完成,我們需要總線對我們的請求做出響應。

但是有時候我們不需要在同一請求/響應中立即執行一個動作的結果,只要異步執行這個動作,比如發送電子郵件。在這種情況下,我們使用發布/訂閱模式,以異步方式發送電子郵件,並避免讓用戶等待發送電子郵件。

 

4、領域事件驅動是如何運行的呢?

這個時候,就用到之前我畫的圖了,中介者模式下,上半部的命令總線已經說完,今天說另一半事件總線:

 

當然這里也有一個網上的栗子,很不錯:

 

 從圖中我們也可以看到,事件驅動的工作流程呢,在命令模式下,主要是在我們的命令處理程序中出現,在我們對數據進行持久化操作的時候,作為一個后續活動事件來存在,比如我們今天要實現的兩個處理工作:

1、通知信息的收集(之前我們是采用的緩存 Memory 來實現的);

2、領域通知處理程序(比如發郵件等);

 

這個時候,如果你對事件驅動有了一定的理解的話,你就會問,那我們在項目中具體的應該使用呢,請往下看。

 

二、創建事件總線

這個整體流程其實和命令總線分發很像,所以原理就不分析了,相信你如果看了之前的兩篇文章的話,一定能看懂今天的內容的。

1、定義領域事件標識基類

就如上邊我們說到的,我們可以定義一個接口,也可以定義一個抽象類,我比較習慣用抽象類,在核心領域層 Christ3D.Domain.Core 中的Events 文件夾中,新建Event.cs 事件基類:

namespace Christ3D.Domain.Core.Events
{
    /// <summary>
    /// 事件模型 抽象基類,繼承 INotification
    /// 也就是說,擁有中介者模式中的 發布/訂閱模式
    /// </summary>
    public abstract class Event : INotification
    {
        // 時間戳
        public DateTime Timestamp { get; private set; }
        
        // 每一個事件都是有狀態的
        protected Event()
        {
            Timestamp = DateTime.Now;
        }
    }
}

 

2、定義事件總線接口

在中介處理接口IMediatorHandler中,定義引發事件接口,作為發布者,完整的 IMediatorHandler.cs 應該是這樣的

namespace Christ3D.Domain.Core.Bus
{
    /// <summary>
    /// 中介處理程序接口
    /// 可以定義多個處理程序
    /// 是異步的
    /// </summary>
    public interface IMediatorHandler
    {
        /// <summary>
        /// 發送命令,將我們的命令模型發布到中介者模塊
        /// </summary>
        /// <typeparam name="T"> 泛型 </typeparam>
        /// <param name="command"> 命令模型,比如RegisterStudentCommand </param>
        /// <returns></returns>
        Task SendCommand<T>(T command) where T : Command;

        /// <summary>
        /// 引發事件,通過總線,發布事件
        /// </summary>
        /// <typeparam name="T"> 泛型 繼承 Event:INotification</typeparam>
        /// <param name="event"> 事件模型,比如StudentRegisteredEvent,</param>
        /// 請注意一個細節:這個命名方法和Command不一樣,一個是RegisterStudentCommand注冊學生命令之前,一個是StudentRegisteredEvent學生被注冊事件之后
        /// <returns></returns>
        Task RaiseEvent<T>(T @event) where T : Event;

    }
}

 

 

3、實現總線分發接口

 在基層設施總線層的記憶總線 InMemoryBus.cs 中,實現我們上邊的事件分發總線接口:

 /// <summary>
 /// 引發事件的實現方法
 /// </summary>
 /// <typeparam name="T">泛型 繼承 Event:INotification</typeparam>
 /// <param name="event">事件模型,比如StudentRegisteredEvent</param>
 /// <returns></returns>
 public Task RaiseEvent<T>(T @event) where T : Event
 {
     // MediatR中介者模式中的第二種方法,發布/訂閱模式
     return _mediator.Publish(@event);
 }

 

注意這里使用的是中介模式的第二種——發布/訂閱模式,想必這個時候就不用給大家解釋為什么要使用這個模式了吧(提示:不需要對請求進行必要的響應,與請求/響應模式做對比思考)。現在我們把事件總線定義(是一個發布者)好了,下一步就是如何定義事件模型和處理程序了也就是訂閱者,如果上邊的都看懂了,請繼續往下走。

 

三、事件模型的處理與使用

 可能這句話不是很好理解,那說人話就是:我們之前每一個領域模型都會有不同的命令,那每一個命令執行完成,都會有對應的后續事件(比如注冊和刪除用戶肯定是不一樣的),當然這個是看具體的業務而定,就比如我們的訂單領域模型,主要的有下單、取消訂單、刪除訂單等。

我個人感覺,每一個命令模型都會有對應的事件模型,而且一個命令處理方法可能有多個事件方法。具體的請看:

1、定義添加Student 的事件模型

當然還會有刪除和更新的事件模型,這里就用添加作為栗子,在領域層 Christ3D.Domain 中,新建  Events 文件夾,用來存放我們所有的事件模型,

因為是 Student 模型,所以我們在 Events 文件夾下,新建 Student 文件夾,並新建 StudentRegisteredEvent.cs 學生添加事件類:

namespace Christ3D.Domain.Events
{
    /// <summary>
    /// Student被添加后引發事件
    /// 繼承事件基類標識
    /// </summary>
    public class StudentRegisteredEvent : Event
    {
        // 構造函數初始化,整體事件是一個值對象
        public StudentRegisteredEvent(Guid id, string name, string email, DateTime birthDate, string phone)
        {
            Id = id;
            Name = name;
            Email = email;
            BirthDate = birthDate;
            Phone = phone;
        }
        public Guid Id { get; set; }
        public string Name { get; private set; }
        public string Email { get; private set; }
        public DateTime BirthDate { get; private set; }
        public string Phone { get; private set; }
    }
}

 

2、定義領域事件的處理程序Handler

這個和我們的命令處理程序一樣,只不過我們的命令處理程序是總線在應用服務層分發的,而事件處理程序是在領域層的命令處理程序中被總線引發的,可能有點兒拗口,看看下邊代碼就清楚了,就是一個引用場景的順序問題。

在領域層Chirst3D.Domain 中,新建 EventHandlers 文件夾,用來存放我們的事件處理程序,然后新建 Student事件模型的處理程序 StudentEventHandler.cs:

 

namespace Christ3D.Domain.EventHandlers
{
    /// <summary>
    /// Student事件處理程序
    /// 繼承INotificationHandler<T>,可以同時處理多個不同的事件模型
    /// </summary>
    public class StudentEventHandler :
        INotificationHandler<StudentRegisteredEvent>,
        INotificationHandler<StudentUpdatedEvent>,
        INotificationHandler<StudentRemovedEvent>
    {
        // 學習被注冊成功后的事件處理方法
        public Task Handle(StudentRegisteredEvent message, CancellationToken cancellationToken)
        {
            // 恭喜您,注冊成功,歡迎加入我們。

            return Task.CompletedTask;
        }

        // 學生被修改成功后的事件處理方法
        public Task Handle(StudentUpdatedEvent message, CancellationToken cancellationToken)
        {
            // 恭喜您,更新成功,請牢記修改后的信息。

            return Task.CompletedTask;
        }

        // 學習被刪除后的事件處理方法
        public Task Handle(StudentRemovedEvent message, CancellationToken cancellationToken)
        {
            // 您已經刪除成功啦,記得以后常來看看。

            return Task.CompletedTask;
        }
    }
}

相信大家應該都能看的明白,在上邊的注釋已經很清晰的表達了響應的作用,如果有看不懂,咱們可以一起交流。

好啦,現在第二步已經完成,剩下最后一步:如何通過事件總線分發我們的事件模型了。

 

3、在事件總線EventBus中引發事件

這個使用起來很簡單,主要是我們在命令處理程序中,處理完了持久化以后,接下來調用我們的事件總線,對不同的事件模型進行分發,就比如我們的 添加Student 命令處理程序方法中,我們通過工作單元添加成功后,需要做下一步,比如發郵件,那我們就需要這么做。

在命令處理程序 StudentCommandHandler.cs 中,完善我們的提交成功的處理:

 // 持久化
 _studentRepository.Add(customer);

 // 統一提交
 if (Commit())
 {
     // 提交成功后,這里需要發布領域事件
     // 比如歡迎用戶注冊郵件呀,短信呀等
     Bus.RaiseEvent(new StudentRegisteredEvent(customer.Id, customer.Name, customer.Email, customer.BirthDate,customer.Phone));
 }

這樣就很簡單的將我們的事件模型分發到了事件總線中去了,這個時候記得要在 IoC 原生注入類NativeInjectorBootStrapper中,進行注入。關於觸發過程下邊我簡單說一下。

 

4、整體事件驅動執行過程

 說到了這里,你可能發現和命令總線很相似,也可能不是很懂,簡單來說,整體流程是這樣的:

1、首先我們在命令處理程序中調用事件總線來引發事件  Bus.RaiseEvent(........);

2、然后在Bus中,將我們的事件模型進行包裝成固定的格式   _mediator.Publish(@event);

3、然后通過注入的方法,將包裝后的事件模型與事件處理程序進行匹配,系統執行事件模型,就自動實例化事件處理程序 StudentEventHandler;

4、最后執行我們Handler 中各自的處理方法 Task Handle(StudentRegisteredEvent message)。

希望正好也溫習下命令總線的執行過程。

 

5、依賴注入事件模型和處理程序

 // Domain - Events
 // 將事件模型和事件處理程序匹配注入
 services.AddScoped<INotificationHandler<StudentRegisteredEvent>, StudentEventHandler>();
 services.AddScoped<INotificationHandler<StudentUpdatedEvent>, StudentEventHandler>();
 services.AddScoped<INotificationHandler<StudentRemovedEvent>, StudentEventHandler>();

 

這個時候,我們DDD領域驅動設計核心篇的第一部分就是這樣了,還剩下最后的,事件驅動的事件源事件存儲/回溯,我們下一講再說。

 

接下來咱們說說領域通知,為什么要說領域通知呢,大家應該還記得我們之前將錯誤信息放到了內存中,無論是操作還是業務上都很嚴重的問題,肯定是不可取的。那我們應該采用什么辦法呢,欸?!沒錯,你會發現,通過上邊的事件驅動設計,發現領域通知我們也可以采用這個方法,首先是多個模型之間相互通訊,但又不相互引用;而且也在命令處理程序中,對信息進行分發,和發郵件很類似,那具體如何操作呢,請往下看。

 

四、事件分發的另一個用途 —— 領域通知

1、領域通知模型 DomainNotification 

 這個通知模型,就像是一個消息隊列一樣,在我們的內存中,通過通知處理程序進行發布和使用,有自己的生命周期,當被訪問並調用完成的時候,會手動對其進行回收,以保證數據的完整性和一致性,這個就很好的解決了咱們之前用Memory緩存通知信息的弊端。

在我們的核心領域層 Christ3D.Domain.Core 中,新建文件夾 Notifications ,然后添加領域通知模型 DomainNotification.cs:

namespace Christ3D.Domain.Core.Notifications
{
    /// <summary>
    /// 領域通知模型,用來獲取當前總線中出現的通知信息
    /// 繼承自領域事件和 INotification(也就意味着可以擁有中介的發布/訂閱模式)
    /// </summary>
    public class DomainNotification : Event
    {
        // 標識
        public Guid DomainNotificationId { get; private set; }
        // 鍵(可以根據這個key,獲取當前key下的全部通知信息)
        // 這個我們在事件源和事件回溯的時候會用到,伏筆
        public string Key { get; private set; }
        // 值(與key對應)
        public string Value { get; private set; }
        // 版本信息
        public int Version { get; private set; }

        public DomainNotification(string key, string value)
        {
            DomainNotificationId = Guid.NewGuid();
            Version = 1;
            Key = key;
            Value = value;
        }
    }
}

 

 

2、領域通知處理程序 DomainNotificationHandler

該處理程序,可以理解成,就像一個類的管理工具,在每次對象生命周期內 ,對領域通知進行實例化,獲取值,手動回收,這樣保證了每次訪問的都是當前實例的數據。

 還是在文件夾 Notifications 下,新建處理程序 DomainNotificationHandler.cs:

namespace Christ3D.Domain.Core.Notifications
{
    /// <summary>
    /// 領域通知處理程序,把所有的通知信息放到事件總線中
    /// 繼承 INotificationHandler<T>
    /// </summary>
    public class DomainNotificationHandler : INotificationHandler<DomainNotification>
    {
        // 通知信息列表
        private List<DomainNotification> _notifications;

        // 每次訪問該處理程序的時候,實例化一個空集合
        public DomainNotificationHandler()
        {
            _notifications = new List<DomainNotification>();
        }

        // 處理方法,把全部的通知信息,添加到內存里
        public Task Handle(DomainNotification message, CancellationToken cancellationToken)
        {
            _notifications.Add(message);
            return Task.CompletedTask;
        }
        
        // 獲取當前生命周期內的全部通知信息
        public virtual List<DomainNotification> GetNotifications()
        {
            return _notifications;
        }

        // 判斷在當前總線對象周期中,是否存在通知信息
        public virtual bool HasNotifications()
        {
            return GetNotifications().Any();
        }

        // 手動回收(清空通知)
        public void Dispose()
        {
            _notifications = new List<DomainNotification>();
        }
    }
}

到了目前為止,我們的DDD領域驅動設計中的核心領域層部分,已經基本完成了(還剩下下一篇的事件源、事件回溯):

 

3、在命令處理程序中發布通知

 我們定義好了領域通知的處理程序,我們就可以像上邊的發布事件一樣,來發布我們的通知信息了。這里用一個栗子來試試:

在學習命令處理程序 StudentCommandHandler.cs 中的 RegisterStudentCommand 處理方法中,完善:

 // 判斷郵箱是否存在
 // 這些業務邏輯,當然要在領域層中(領域命令處理程序中)進行處理
 if (_studentRepository.GetByEmail(customer.Email) != null)
 {
     ////這里對錯誤信息進行發布,目前采用緩存形式
     //List<string> errorInfo = new List<string>() { "該郵箱已經被使用!" };
     //Cache.Set("ErrorData", errorInfo);

     //引發錯誤事件
     Bus.RaiseEvent(new DomainNotification("", "該郵箱已經被使用!"));
     return Task.FromResult(new Unit());
 }

這個時候,我們把錯誤通知信息在事件總線中發布出去,剩下的就是需要在別的任何地方訂閱即可,還記得哪里么,沒錯就是我們的自定義視圖組件中,我們需要訂閱通知信息,展示在頁面里。

注意:我們還要修改一下之前我們的命令處理程序基類 CommandHandler.cs 的驗證信息收集方法,因為之前是用緩存來實現的,我們這里也用發布事件來實現:

 //將領域命令中的驗證錯誤信息收集
 //目前用的是緩存方法(以后通過領域通知替換)
 protected void NotifyValidationErrors(Command message)
 {
     List<string> errorInfo = new List<string>();
     foreach (var error in message.ValidationResult.Errors)
     {
         //errorInfo.Add(error.ErrorMessage);      
         //將錯誤信息提交到事件總線,派發出去
         _bus.RaiseEvent(new DomainNotification("", error.ErrorMessage));
     }
     //將錯誤信息收集一:緩存方法(錯誤示范)
     //_cache.Set("ErrorData", errorInfo);
 }

 

 

4、在視圖組件中獲取通知信息

這個很簡單,之前我們用的是注入 IMemory 的方式,在緩存中獲取,現在我們通過注入領域通知處理程序來實現,在視圖組件 AlertsViewComponent.cs 中:

    public class AlertsViewComponent : ViewComponent
    {
        // 緩存注入,為了收錄信息(錯誤方法,以后會用通知,通過領域事件來替換)
        // private IMemoryCache _cache;
        // 領域通知處理程序
        private readonly DomainNotificationHandler _notifications;

        // 構造函數注入
        public AlertsViewComponent(INotificationHandler<DomainNotification> notifications)
        {
            _notifications = (DomainNotificationHandler)notifications;
        }

        /// <summary>
        /// Alerts 視圖組件
        /// 可以異步,也可以同步,注意方法名稱,同步的時候是Invoke
        /// 我寫異步是為了為以后做准備
        /// </summary>
        /// <returns></returns>
        public async Task<IViewComponentResult> InvokeAsync()
        {
            // 從通知處理程序中,獲取全部通知信息,並返回給前台
            var notificacoes = await Task.FromResult((_notifications.GetNotifications()));
            notificacoes.ForEach(c => ViewData.ModelState.AddModelError(string.Empty, c.Value));

            return View();
        }
    }

 

5、StudentController 判斷是否有通知信息

 通過注入的方式,把 INotificationHandler<DomainNotification> 注入控制器,然后因為這個接口可以實例化多個對象,那我們就強類型轉換成 DomainNotificationHandler:

 

這里要說明下,記得要對事件處理程序注入,才能使用:

 // 將事件模型和事件處理程序匹配注入
 services.AddScoped<INotificationHandler<DomainNotification>, DomainNotificationHandler>();

 

 

五、結語

好啦,今天的講解基本就到這里了,今天重點說明了,我們如何使用事件總線,已經事件驅動模型下如何定義事件模型和事件處理程序,如果你都看懂了呢,這里可以簡單回想一下以下幾個問題:

1、為什么要定義事件驅動呢?(提示詞:業務分離)

2、我們是在哪里發布這些事件的呢?(提示詞:.publish()方法) 

3、事件驅動中的生命周期是從哪里開始到哪里接受的?(提示:處理程序Handler)

 

如果你對以上的內容還是比較困惑呢,這里有兩個文章可以參考,當然,多溝通才是關鍵!

https://www.cnblogs.com/lori/p/4080426.html

https://blog.csdn.net/sD7O95O/article/details/79609305

 

六、GitHub & Gitee

https://github.com/anjoy8/ChristDDD

https://gitee.com/laozhangIsPhi/ChristDDD 

 

 

--END


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM