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中,不利于咱们进行读写分离。

以上请你们参考。

相关文章
相关标签/搜索