今天咱們來分析另外一個開源的CQRS+ES項目:Equinox。該項目能夠在github上下載並直接本地運行,項目地址:https://github.com/EduardoPires/EquinoxProject,該項目是基於 .net core 2.2的,開發語言、編碼方式比Diary.CQRS更加新潮(CQRS+ES項目解析-Diary.CQRS),也更符合咱們如今的開發習慣。html
首先經過github獲取到項目源代碼,打開項目文件,你會看到以下分層:git
經過項目分層,咱們已經對該項目有了一個大體的輪廓,當從Presentation、Services層接收到來自客戶端的請求後,將會調用Application層的應用程序服務,應用程序服務將數據進行封裝和轉換,而後交給Domain層進行處理,Domain層則調用Infra相關的方法完成持久化、消息發佈等功能。github
Domain層是Equinox項目的核心部分,Entity/ValueObject、Repository、UoW、Command、Event、EventStore等均在該層進行定義,咱們來看一下。數據庫
實體對象,定義以下:app
public abstract class Entity { public Guid Id { get; protected set; } public override bool Equals(object obj) { //...... } public static bool operator ==(Entity a, Entity b) { //...... } public static bool operator !=(Entity a, Entity b) { //...... } public override int GetHashCode() { //...... } public override string ToString() { //...... } }
每個實體對象都要具有ID屬性,用來標記惟一性;重寫了Equals方法、定義了==、!=操做符,用於兩個對象的比較;重寫了ToString方法、GetHashCode方法。dom
值對象,與實體對象進行區分,值對象沒有Id屬性。定義以下:ide
public abstract class ValueObject<T> where T : ValueObject<T> { public override bool Equals(object obj) { //...... } protected abstract bool EqualsCore(T other); public override int GetHashCode() { //...... } protected abstract int GetHashCodeCore(); public static bool operator ==(ValueObject<T> a, ValueObject<T> b) { //...... } public static bool operator !=(ValueObject<T> a, ValueObject<T> b) { //...... } }
與Entity類似,定義了一些基本的操做方法。模塊化
數據倉儲,用來進行數據訪問,定義以下:ui
public interface IRepository<TEntity> : IDisposable where TEntity : class { void Add(TEntity obj); TEntity GetById(Guid id); IQueryable<TEntity> GetAll(); void Update(TEntity obj); void Remove(Guid id); int SaveChanges(); }
定義了對數據的基本操做,添加、更新、刪除、查詢等方法編碼
工做單元,定義以下:
public interface IUnitOfWork : IDisposable { bool Commit(); }
定義了Commit方法,當業務邏輯執行完成用,用於數據庫事物
CQRS和ES的核心部分,Command、Event被定義爲消息,擁有共同的基類Message,分別定義以下:
Command:
public abstract class Command : Message { public DateTime Timestamp { get; private set; } public ValidationResult ValidationResult { get; set; } protected Command() { Timestamp = DateTime.Now; } public abstract bool IsValid(); }
Event:
public abstract class Event : Message, INotification { public DateTime Timestamp { get; private set; } protected Event() { Timestamp = DateTime.Now; } }
與Command、Event對應的處理程序用來處理相應的業務邏輯,此處不在介紹。感興趣的朋友能夠參照上篇文章進行了解。
EventStore也是ES的核心內容,負責對事件的存儲、提取工做。在Equinox項目中,EventStore的定義以下:
public interface IEventStore { void Save<T>(T theEvent) where T : Event; }
額?只有一個Save方法,這不符合邏輯,只能進行事件的存儲,而沒有事件的查詢。經過查閱項目的其它代碼,我發現事件的查詢則是經過EventStoreRepository來實現的,這一點不太符合咱們的開放封閉原則和模塊化思想。做者多是想着對事件的操做也遵循CQRS模式嗎?這就未可知了。
消息通訊,Equinox項目中使用MediatR實現的基於內存的消息通訊。定義以下:
public interface IMediatorHandler { Task SendCommand<T>(T command) where T : Command; Task RaiseEvent<T>(T @event) where T : Event; }
基礎設施層裏面,定義了Domain層接口的實現,例如Data中實現了倉儲、工做單元,Bus中實現了InMemoryBus等。因爲都是很是簡單的實現,再也不展開介紹。
應用程序服務層有兩個做用,封裝底層(Infra、Domain)的操做,對UI層(Presentation、Services)數據進行轉換,它是UI層與Domain層的橋樑。此處再也不展開介紹。
Equinox項目中,UI層由兩部分組成,分別是Presentation和Services,其中展現層提供了界面操做的功能,Services層提供了接口訪問的功能,這兩個項目採用MVC和WebApi技術,再也不展開介紹。
經過分析Equinox項目的結構和代碼,咱們能夠發現,這個項目並非很完善,做者所說的不要用在生產環境是實話。
在這個項目中,對於ES的實現並非很優雅,首先EventStore的操做,未提供查詢事件的接口,從而致使了須要經過Repository來獲取Event,破壞了EventStore的完整性;其次該項目沒有完成事件重放功能,咱們只能經過事件查看到數據的變動,可是沒法經過重放來獲取項目的某個時段的狀態的功能;最後,Equinox項目未實現讀寫分離,對於數據的查詢和增長更新等操做都混合在一個Repository中,不利於咱們進行讀寫分離。
以上請你們參考。