Event Sourcing - ENode(三)


接上一篇

http://www.cnblogs.com/dopeter/p/4903328.html

老板昨天在第二篇介紹中回復代碼和文字無法一一對應。為了更好的讓老板為大家解惑,把第二篇最后的猜測的問題搞清楚后,就補上其他文字說明的代碼圖。

在上篇中泛泛介紹了Commanding,比較跳躍,目前是想到哪寫到哪,后續分門別類的整理。

 

在后續中會補全ENode框架的裝配關系,其實作者的接口命名已經非常清楚了。

無論作者使用了什么樣的裝配的設計模式,目的都是為了更好的擴展與維護。一般能直接組合的就直接組合,能隔離的就直接隔離,如果遇到無法處理的情況有句經典的話,當我們遇到無法解決的問題的時候,就增加一個中間層來專門解決這個問題。其實和現實中是很匹配的,還是借鑒了現實的智慧啊。同事A,同事B、我,當A與B在一件事有爭執的時候,那我就充當Broker的角色吧,如果這件事非常重要,很有可能出現矛盾,於是我可能會求助他人,例如同事C或者領導,讓他們分別與A、B溝通,又也許我會集中精神,在A的面前與B的面前使用不同的方式來溝通,那么就是Proxy了。呵呵,想到了就寫下來了。繼續正題。

 

EDA


目前很火的架構模型,這里的Event不是DDD中的Domain Event,純粹是指這種架構風格的Event,當然某些情況下我們可以這么認為,不過這里先不套用。

 

假如現在在一個分布式的系統中,有2個子系統實例,ServiceA與ServiceB。

假如ServiceA的某一個功能是創建Account,ServiceB的某一個功能是發送郵件。

現在有個系統級的功能是當一個Account被創建后,向這個Account發送郵件。

那么ServiceA創建好Account后,會通知ServiceB發送郵件。它們之間會進行通訊。

 

Event可以認為是一種它們之間傳遞消息的模型,例如ServiceA創建了一個Account並發布一個Event叫做OnAccountCreated,當然在我們實際的實現中,會被描述成各種不同的類,在ENode中這個Event消息被作者定義為Message,Message就是一個Event,在一個Event里面會包含這個Event的信息,例如OnAccountCreated事件里面包含了新增賬戶的郵箱。

還有另外一種模型,就是我們傳統實現的模型,可以借鑒CQRS的Command/Query,例如ServiceA創建了一個Account,調用ServiceB的發送郵件的方法,調用發送郵件就是一個Command,ServiceA命令ServiceB發送郵件。

是不是覺得有點像Pub/Sub與Request/Reply,對應的實現是RPC和MOM。

可以這么理解,但也不完全是。

一般我們使用Request/Reply的RPC框架,兩邊定義契約,假如我們這里的契約是面向功能,例如ServiceB定義一個SendMail(Account account);當ServiceA完成創建Account后就可以調用這個接口發送郵件。

如果我們使用Request/Reply能不能實現Event呢,完全可以,ServiceA使用RPC框架發送一個OnAccountCreated的事件至ServiceB,在OnAccountCreated事件中包含了Account的信息,ServiceB開始發送郵件。Event的處理有很多優雅實現,最簡單暴力的方式是直接規定一個通用接口,根據傳遞過來的事件處理邏輯。

問題就在這里,使用RPC我們需要知道對方的契約,即便是REST我們也要知道URL即資源地址。通過Event的Wrapper我們實際上是消除了RPC當中雙方的契約,REST這邊的資源地址。我們的契約面向的是什么角度,也就是解耦了什么角度,例如如果我們契約是業務型契約,就是解耦了業務也可以認為是功能,ServiceA不用知道ServiceB有發送郵件的功能。ServiceA只需要發送一個Event至ServiceB即可,也許不是OnAccountCreated事件。

這樣通過Event我們實現了消息層面的解耦,本質上Event是一個消息的抽象。Event包裹了真實的業務消息,再次證明中間層這句話的適用性。不過這句話還有后面半截,這里不扯遠了。

但是這時ServiceA是必須知道ServiceB的,MOM可以不讓ServiceA不必知道ServiceB。

所以MOM+Event實現了分布式的EDA,對ServiceA來說,不用知道在創建完Account后下一步需要做什么,由誰來做。通過MOM來隔離主從關系。

 

從另外一個角度來看Request/Reply以及Pub/Sub。

Request/Reply發送Command,還是剛才的場景,ServiceB需要返回結果或者不返回結果,從組件運行層面來看,它是同步的。我們可以這樣實現,使用Event,例如ServiceA發送OnAccountCreated事件至ServiceB,ServiceB發送OnMailSendCompleted事件至ServiceA,如果這個業務場景ServiceA必須得到郵件發送成功才能執行后續操作,那么仍然是同步的,另外種解釋我們可以認為是同步非阻塞的。

Pub/Sub+Event,我們也就可以認為是異步非阻塞的。

 

那么EDA的真正威力就體現出來,可擴展性,吞吐量,都會上升。

 

EDA In ENode


 

說了很多EDA的話題,還是來ENode里面看看作者的實現。

前面介紹過,ENode分布式的粒度,在開發者應用層范圍內定義了4種Event :Application Message、Command、Domain Event、Exception,可以將他們認為是EDA中Event在CQRS框架場景中的實例,其實這么說並不准確,應該是在ENode框架中作者定義好要用Event包裹的框架消息類型。EDA的組合還會有個MOM,則是作者自己實現並開源的EQueue。下圖中所示的不是EQueue項目,實際上是EQueue在ENode項目中的Proxy。

 

一目了然,分別是4種消息的Pub/Sub。讓我們看看其中一個的實現,就更清楚了。

先看最簡單的ApplicationMessage

 

Consumer 消費者,Subscribe(string topic);訂閱一個主題,這里就能夠看到MOM的存在。

IQueueMessageHandler.Handle(QueueMessage queueMessage, IMessageContext context)方法,Consume還充當了一個Dispatcher的角色。

 

 

Publisher 發布者,PublishAsync方法,發布一個消息,這里為什么沒有Topic呢,如下圖主題。

 

作者已經拆分出去了,在每一個子系統里面可以定義開始所說4種消息類型的不同。我們可以自由的組合,例如將DDD中的應用層定義為一個Project,Domain定義為一個Project,橫向或者縱向的擴展都是可行的,其實可以對應目前比較火的一種架構模型,微服務。

 

讓我們繼續看看Command

 

Consumer 消費者

CommandMessage EQueue傳遞的信息,如下圖所示

注意ReplyAddress,這是后面介紹要用到的屬性。

 

CommandExecutedMessage 已經被處理過的Command消息

CommandResultProcessor 命令結果處理者 在這里不僅包括Command被處理的結果,還可以處理當前這條Command觸發的Domain Event處理的結果,如下圖所示。

可以通過CommandReplyType判別是Command還是DomainEvent的回執。

 

執行一個Command並獲得這條Command處理的結果,是在執行一個Command時指定的。如下圖

這里實際上就是前面介紹EDA所描述的Request/Reply+Event的方式,可以是同步非阻塞也可以是異步非阻塞,如下圖所示

一般都遵守CQRS的原則,Command無返回並且是異步,作者這里通過Fututre的方式來獲取Command執行的回執,針對的是一定要得到結果之后再繼續下面邏輯的場景。有時可能也想直接得到結果,例如上圖所示。這也是作者考慮到很多場景但不生搬硬套CQRS。

 

作者實現回執的方式如下面2張圖所示

 

Consumer接收到消息后,處理完畢后,如果發現CommandMessage中的ReplyAddress存在,則通過SendReplyService向這個地址發送回執消息。

 

剩下的Domain Event和Exception也是差不多的Pub/Sub的組成。就不一一介紹了。

 

作者關於博文的說明


 

作者的說明在回復中,方便感興趣的朋友查看,就復制上來了。

 

By netfocus:

Application Message、Command、Domain Event、Exception這些是ENode中定義的4種消息,他們都實現了IMessage接口。IMessage是ENode中定義的一個通用的 消息接口。但和EQueue中的消息是兩個層次的。EQueue中的消息的內容(payload)才是ENode中的IMessage。

架 構層面,一般流行的就兩種架構:SOA,EDA。SOA屬於RPC風格,一般是阻塞的,高並發時,吞吐量由於服務之間有依賴關系,所以整個系統的吞吐量受 限於最慢的服務的吞吐量;如果是EDA,屬於PUB-SUB的風格,服務之間完全解耦,通過消息的topic來發生邏輯上的關系。系統的吞吐量不受任何一 個節點的限制;具體使用哪種場景要看情況而定,一般系統之間交互,還是用SOA風格的,系統內部的組件之間通信,我覺得EDA更好。

ENode.EQueue 是一個防腐層。用於把ENode和EQueue連接起來,但確保ENode,EQueue相互不知道對方的存在。ENode是一個EDA架構的風格框架, 所以需要一個分布式消息中間件,EQueue就是我開發的分布式消息中間件。如果我們要用其他的消息中間件,可以自己和其他消息中間件整合即可,比如寫一 個ENode.RabbitMQ,ENode.Kafka,ENode.RocketMQ

ICommandService中提供的幾個方 法,比傳統的CQRS架構的定義確實要豐富一點,目的也是為了增加框架的實用性。具體使用哪個方法由開發者自己決定。通常一般我們在后台管理系統,希望等 讀庫也更新后才返回Command的,則可以調用ExecuteAsync並設置為等event也處理完后再返回的方式。如果是前台,用戶不需要立即知道 處理結果的場景,則建議用SendAsync或Send即可。作為一個CQRS框架,我鼓勵大家盡量考慮使用無返回值的方式,否則CQRS的效果就會打折 扣。

ENode作為一個框架,可以使用很多的OO特性,我覺得所有的OO設計原則或設計模式的核心就是隔離變化點,所以當我設計框架時, 首先要定義接口,明確接口的職責,然后接口實現類里發現有些東西是變化的,則進一步提煉其他接口出來,最后可以確保任何實現類內部使用的都是接口,這樣整 個框架可以說任何地方都可以替代,增加了框架的可擴展性。

 


免責聲明!

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



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