zoukankan      html  css  js  c++  java
  • CQRS+ES项目解析-Equinox

    今天我们来分析另一个开源的CQRS+ES项目:Equinox。该项目可以在github上下载并直接本地运行,项目地址:https://github.com/EduardoPires/EquinoxProject,该项目是基于 .net core 2.2的,开发语言、编码方式比Diary.CQRS更加新潮(CQRS+ES项目解析-Diary.CQRS),也更符合我们现在的开发习惯。

    项目概览

    首先通过github获取到项目源代码,打开项目文件,你会看到如下分层:

    • Presentation:展示层,UI在该层实现
    • Services:WebApi在该层实现,同样隶属于UI
    • Application:应用程序服务层,提供了对Domain层接口的封装,注重数据交换,DTO对象在该层定义
    • Domain:领域层,项目的核心部分,领域对象、领域服务在该层实现
    • Infra:基础设施层,项目的公共部分(数据访问)、切片(身份认证、消息发布、依赖注入)部分在该层实现

    通过项目分层,我们已经对该项目有了一个大致的轮廓,当从Presentation、Services层接收到来自客户端的请求后,将会调用Application层的应用程序服务,应用程序服务将数据进行封装和转换,然后交给Domain层进行处理,Domain层则调用Infra相关的方法完成持久化、消息发布等功能。

    Domain层

    Domain层是Equinox项目的核心部分,Entity/ValueObject、Repository、UoW、Command、Event、EventStore等均在该层进行定义,我们来看一下。

    Entity对象

    实体对象,定义如下:

    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方法。

    ValueObject

    值对象,与实体对象进行区分,值对象没有Id属性。定义如下:

    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

    数据仓储,用来进行数据访问,定义如下:

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

    以上请大家参考。

  • 相关阅读:
    Ubuntu 20.04 不能远程连接
    CentOS 6.8 设置开机自动联网
    JSON 语法
    用友U8 | 【成本管理】用友U8卷积运算时警告提示:‘’有未记账非委外加工入库单代管挂账确认单‘’
    用友U8 | 【总账】总账结账时,对账不平
    用友U8 | 【应收款管理】取消核销操作
    用友U8 | 【总账】账簿明细账打印,选择科目打印,页数范围超过了430页,之后的内容都显示不出来
    用友U8 | 【存货核算】存货模块删除凭证时提示:当前凭证已经有实时核销处理,不能被作废(或删除)!
    用友U8 | 【存货核算】存货核算模块,凭证处理,查询凭证时,会计年度选择不到2021年度
    用友U8 | 【总账】科目辅助总账与科目辅助明细账数据不一样
  • 原文地址:https://www.cnblogs.com/youring2/p/11110753.html
Copyright © 2011-2022 走看看