CQRS+ES項目解析-Equinox

今天咱們來分析另外一個開源的CQRS+ES項目:Equinox。該項目能夠在github上下載並直接本地運行,項目地址:https://github.com/EduardoPires/EquinoxProject,該項目是基於 .net core 2.2的,開發語言、編碼方式比Diary.CQRS更加新潮(CQRS+ES項目解析-Diary.CQRS),也更符合咱們如今的開發習慣。html

項目概覽

首先經過github獲取到項目源代碼,打開項目文件,你會看到以下分層:git

  • Presentation:展現層,UI在該層實現
  • Services:WebApi在該層實現,一樣隸屬於UI
  • Application:應用程序服務層,提供了對Domain層接口的封裝,注重數據交換,DTO對象在該層定義
  • Domain:領域層,項目的核心部分,領域對象、領域服務在該層實現
  • Infra:基礎設施層,項目的公共部分(數據訪問)、切片(身份認證、消息發佈、依賴注入)部分在該層實現

經過項目分層,咱們已經對該項目有了一個大體的輪廓,當從Presentation、Services層接收到來自客戶端的請求後,將會調用Application層的應用程序服務,應用程序服務將數據進行封裝和轉換,而後交給Domain層進行處理,Domain層則調用Infra相關的方法完成持久化、消息發佈等功能。github

Domain層

Domain層是Equinox項目的核心部分,Entity/ValueObject、Repository、UoW、Command、Event、EventStore等均在該層進行定義,咱們來看一下。數據庫

Entity對象

實體對象,定義以下: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

ValueObject

值對象,與實體對象進行區分,值對象沒有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類似,定義了一些基本的操做方法。模塊化

Repository

數據倉儲,用來進行數據訪問,定義以下: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();
}

定義了對數據的基本操做,添加、更新、刪除、查詢等方法編碼

UoW

工做單元,定義以下:

public interface IUnitOfWork : IDisposable
{
    bool Commit();
}

定義了Commit方法,當業務邏輯執行完成用,用於數據庫事物

Command/CommandHandler 和 Event/EventHandler

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

EventStore也是ES的核心內容,負責對事件的存儲、提取工做。在Equinox項目中,EventStore的定義以下:

public interface IEventStore
{
    void Save<T>(T theEvent) where T : Event;
}

額?只有一個Save方法,這不符合邏輯,只能進行事件的存儲,而沒有事件的查詢。經過查閱項目的其它代碼,我發現事件的查詢則是經過EventStoreRepository來實現的,這一點不太符合咱們的開放封閉原則和模塊化思想。做者多是想着對事件的操做也遵循CQRS模式嗎?這就未可知了。

Bus

消息通訊,Equinox項目中使用MediatR實現的基於內存的消息通訊。定義以下:

public interface IMediatorHandler
{
    Task SendCommand<T>(T command) where T : Command;
    Task RaiseEvent<T>(T @event) where T : Event;
}

Infra層

基礎設施層裏面,定義了Domain層接口的實現,例如Data中實現了倉儲、工做單元,Bus中實現了InMemoryBus等。因爲都是很是簡單的實現,再也不展開介紹。

Application層

應用程序服務層有兩個做用,封裝底層(Infra、Domain)的操做,對UI層(Presentation、Services)數據進行轉換,它是UI層與Domain層的橋樑。此處再也不展開介紹。

UI層

Equinox項目中,UI層由兩部分組成,分別是Presentation和Services,其中展現層提供了界面操做的功能,Services層提供了接口訪問的功能,這兩個項目採用MVC和WebApi技術,再也不展開介紹。

Equinox項目總結

經過分析Equinox項目的結構和代碼,咱們能夠發現,這個項目並非很完善,做者所說的不要用在生產環境是實話。

在這個項目中,對於ES的實現並非很優雅,首先EventStore的操做,未提供查詢事件的接口,從而致使了須要經過Repository來獲取Event,破壞了EventStore的完整性;其次該項目沒有完成事件重放功能,咱們只能經過事件查看到數據的變動,可是沒法經過重放來獲取項目的某個時段的狀態的功能;最後,Equinox項目未實現讀寫分離,對於數據的查詢和增長更新等操做都混合在一個Repository中,不利於咱們進行讀寫分離。

以上請你們參考。

相關文章
相關標籤/搜索