天天看點

ENode 1.0 - 事件驅動架構(EDA)思想的在架構中如何展現經典DDD的基于領域服務的實作方式enode的事件驅動的實作方式

好了,這篇文章,我主要想介紹的是eda思想在enode架構中如何展現?

一般的應用程式,如果一個使用者動作會涉及多個聚合根的修改,我們通常會在應用層服務中建立一個unit of work,然後,我們可能會設計一個領域服務類,在該領域服務類裡,修改多個聚合根,然後應用層服務将整個unit of work中的修改一次性以事務的方式送出到資料庫。這種方式就是以事務的方式來實作涉及多個聚合根修改的強一緻性。以銀行轉賬這個經典的場景作為分析案例:

ENode 1.0 - 事件驅動架構(EDA)思想的在架構中如何展現經典DDD的基于領域服務的實作方式enode的事件驅動的實作方式
ENode 1.0 - 事件驅動架構(EDA)思想的在架構中如何展現經典DDD的基于領域服務的實作方式enode的事件驅動的實作方式

一次銀行轉賬,最核心的動作就是源賬号轉出錢,目标賬号轉入錢;當然實際的銀行轉賬肯定不是這麼簡單,也肯定不是這麼實作。我拿這個作為例子隻是為了通過這個大家都熟知的簡單例子來分析如果一個使用者場景涉及不止一個聚合根的修改的時候,如果基于經典的ddd的方式,我們是如何實作的。如上面的代碼所示,我們可能會設計一個應用層服務,如上面的ibankaccountservice,該應用層服務裡有一個transfermoney的方法,表示用于實作銀行轉賬的功能;然後該應用層服務會進一步調用一個領域層的轉賬領域服務,就是上面代碼中的transfermoneyservice,按照eric evans所說,領域服務應該是一個以動詞命名的服務,一個領域服務可以明确對應到領域中的一個有業務含義的領域動作,此例就是“轉賬”,是以我設計了一個transfermoneyservice的以動詞來命名的領域服務,該服務的transfermoney方法實作了銀行轉賬的核心業務邏輯。

上面這個例子中,按照經典ddd,我們應該在應用層實作流程控制邏輯以及事務等東西;是以大家可以看到,以上代碼中,我們是先擷取一個unit of work,即上面代碼中的context,最後調用context.savechanges方法,該方法的職責就是将目前上下文的所有修改以事務的方式送出到資料庫。好了,上面這個例子我們分析了經典ddd關于如何實作一個會涉及多個聚合根建立或修改的使用者場景;

我一直說enode是一個基于事件驅動架構(eda,event-driven architecture)的架構。且深藍醫生在前面的回複中也對什麼是事件驅動的架構有疑惑。是以我想說一下我對事件驅動架構的了解。

上面這一段,我簡單介紹了我所了解的eda,以及它的基本的好處。下面我們看看,在enode中,我們是如何利用eda這種原理的。為了簡化,我先用一個簡單的例子說明一下,就用我源代碼中的notesample吧,反正也能一樣說明事件驅動的影子在哪裡。看以下的代碼:

ENode 1.0 - 事件驅動架構(EDA)思想的在架構中如何展現經典DDD的基于領域服務的實作方式enode的事件驅動的實作方式
ENode 1.0 - 事件驅動架構(EDA)思想的在架構中如何展現經典DDD的基于領域服務的實作方式enode的事件驅動的實作方式

上面的例子中,note是一個聚合根,它會響應兩個事件:notecreated, notetitlechanged。要實作事件響應,我們可以通過實作架構提供的ieventhandler<t>接口,就能告訴架構,我要訂閱什麼事件了。

上面代碼中,應該比較詳細的注釋了每段代碼的含義了,應該都能看懂吧。上面這個例子說明了,聚合跟自己的狀态不是在public方法中直接改的,而是基于事件驅動的方式來修改的,是以,大家可以看到,聚合根狀态的修改是在一個内部響應函數中修改的。下面我們再來看一下外部其他對象,如何響應該事件:

ENode 1.0 - 事件驅動架構(EDA)思想的在架構中如何展現經典DDD的基于領域服務的實作方式enode的事件驅動的實作方式
ENode 1.0 - 事件驅動架構(EDA)思想的在架構中如何展現經典DDD的基于領域服務的實作方式enode的事件驅動的實作方式

通過上面兩個簡單的例子,不知道有沒有解釋清楚,在enode架構中,如何展現eda?

總結:

我之是以比較喜歡事件驅動這種思想是基于以下理由:

就是上面我說的解耦+可擴充;

事件可以并行執行;就是說,一個系統中,同時可以有很多事件在并行的産生、傳遞、響應;這樣說,大家可能還了解不了這一點的價值。我說一下并發的概念。通常我們所說的一個網站的并發,比如有5000,是指一個網站在1秒内的所有并發請求數,這麼多并發請求數是針對系統中所有的聚合根的;也就是如果平攤到每個聚合根,那并發修改數一般就很低了,比如每秒隻有10個并發,甚至隻有1個或兩個。這點每個系統有所不同,比如淘寶的商品秒殺活動,那當秒殺開始的時刻,對同一個商品的下單的并發數很高,因為每個商品的每個訂單都意味着要減庫存,是以這個減庫存的并發操作一定很高,實作起來肯定很困難了,不通過可靠的分布式緩存以及樂觀鎖機制,估計很難實作;而比如新浪微網誌上,我們每個人發微網誌,雖然整個新浪微網誌網站的整體并發數很高,因為肯定每秒有非常多的人在寫微網誌,但是我們同時也知道,大家寫的微網誌都是獨立的,沒有共享資源,每發表一條微網誌實際上就是建立一條資料庫記錄而已。是以可以了解為,單個對象無并發;而一般的企業應用或一般的網際網路應用,針對同一資源(同一個聚合根)的并發修改,一般都不高;是以基于這樣的分析和了解,我們知道了,理論上,事件什麼時候可以并行産生和執行,什麼時候必須排隊。就是:如果兩個事件不是同一個聚合根産生的,那就可以并行處理,事件也可以并行持久化;如果是單個聚合根産生的,那必須按照順序被持久化;是以,根據這樣的了解,我們知道了,一個應用程式,除了單個聚合根上的修改隻能串行進行外,其他情況理論上都可以并行執行;這段話說了這麼多關于并發數以及事件并行方面的東西,那究竟知道這些有什麼用呢?很簡單,隻要和傳統的事務模式對比下就知道了,傳統的事務模式,如果要修改多個聚合根,那事務在執行的那一段時間,所有涉及到的聚合根都不能被其他事務所修改;隻有等到目前事務執行完成後,其他事務才能執行;而通過事件的方式,由于我們沒有事務的概念,我們唯一要確定的隻是一個聚合根上産生的事件必須被一個個按順序持久化,這點我們很簡單,比如我們隻要建一個聯合主鍵:聚合根id+事件版本号,然後做樂觀并發控制即可;是以,事件持久化時,排他的粒度比事務要小,這樣的好處是無阻塞;那麼換來的好處就是網站整體的可用性高;但是帶來的壞處是,可能有可能會出現樂觀并發沖突,但這點我們可以通過架構的自動重試功能解決掉;而且,我們也剛分析過,同一個聚合根的并發修改一般是很低的;是以通過事件的方式來達到這種細粒度的對聚合根的修改是非常有意義的。