DDD领域驱动设计——CQRS,MediatR

前言

源码地址:
https://github.com/SkylerSkr/Skr3D

CQRS

随着并发量的增大,往往让我们处理读请求写请求的解决方案完全不同。
例如:在处理读请求的时候用Redis,Es数据仓库等;在处理写请求的时候使用Rabbitmq等消峰。

所以在DDD架构中,通常会将查询命令操作分开,我们称之为CQRS(命令查询的责任分离Command Query Responsibility Segregation),具体落地时,是否将Command和Query分开成两个项目可以看情况决定,大多数情况下放在一个项目可以提高业务内聚性。

那么就有人问了,如果放在两个项目里,业务就不内聚了。如果放在一个项目里,那又怎么算分离呐。

餐厅故事

在讲解决方式之前,先引入一个故事。
老王开了一个餐馆,经常在隔壁家爬上爬下的他自然是文武双全。服务员,厨师,老公都是自己一人担当。
后来餐馆的名气越来越大了,老王自己也不干了,招了很多厨师,每个厨师都只做自己的特色菜。
又找了一个服务员,写了一份菜品清单,让服务员根据清单找指定的厨师做菜。

整理一下:
客人点菜->服务员查看菜品清单->找厨师做菜->给客人送菜

MediatR,中介者模式

上述这个例子,就是典型的中介者模式,服务员作为中介者,来分配给指定厨师做菜。
我们用现成的轮子MediatR,来做中介者模式。
MediatR学习链接
我们直接上代码:
中介者接口,用MediatR实现

    /// 
    /// 中介处理程序接口
    /// 可以定义多个处理程序
    /// 是异步的
    /// 
    public interface IMediatorHandler
    {
        /// 
        /// 发送命令,将我们的命令模型发布到中介者模块
        /// 
        ///  泛型 
        ///  命令模型,比如RegisterStudentCommand 
        /// 
        Task SendCommand(T command) where T : Command;


        /// 
        /// 引发事件,通过总线,发布事件
        /// 
        ///  泛型 继承 Event:INotification
        ///  事件模型,比如StudentRegisteredEvent,
        /// 请注意一个细节:这个命名方法和Command不一样,一个是RegisterStudentCommand注册学生命令之前,一个是StudentRegisteredEvent学生被注册事件之后
        /// 
        Task RaiseEvent(T @event) where T : Event;
    }

    /// 
    /// 一个密封类,实现我们的中介内存总线
    /// 
    public sealed class InMemoryBus : IMediatorHandler
    {
        //构造函数注入
        private readonly IMediator _mediator;

        // 事件仓储服务
        //private readonly IEventStoreService _eventStoreService;

        public InMemoryBus(IMediator mediator)
        {
            _mediator = mediator;
        }
        /// 
        /// 实现我们在IMediatorHandler中定义的接口
        /// 没有返回值
        /// 
        /// 
        /// 
        /// 
        public Task SendCommand(T command) where T : Command
        {
            //这个是正确的
            return _mediator.Send(command);//请注意 入参 的类型
        }

        /// 
        /// 引发事件的实现方法
        /// 
        /// 泛型 继承 Event:INotification
        /// 事件模型,比如StudentRegisteredEvent
        /// 
        public Task RaiseEvent(T @event) where T : Event
        {
            // MediatR中介者模式中的第二种方法,发布/订阅模式
            return _mediator.Publish(@event);
        }
    }

然后是处理程序

    /// 
    /// 领域命令处理程序
    /// 用来作为全部处理程序的基类,提供公共方法和接口数据
    /// 
    public class CommandHandler
    {

        // 注入中介处理接口(目前用不到,在领域事件中用来发布事件)
        private readonly IMediatorHandler _bus;


        /// 
        /// 构造函数注入
        /// 
        /// 
        /// 
        /// 
        public CommandHandler( IMediatorHandler bus)
        {
            _bus = bus;
        }
    }

订单处理程序,指定命令的处理程序,IRequestHandler

    /// 
    /// Order命令处理程序
    /// 用来处理该Order下的所有命令
    /// 注意必须要继承接口IRequestHandler<,>,这样才能实现各个命令的Handle方法
    /// 
    public class OrderCommandHandler : CommandHandler,
        IRequestHandler
    {
        // 注入仓储接口
        private readonly IOrderRepository _OrderRepository;
        // 注入总线
        private readonly IMediatorHandler Bus;

        /// 
        /// 构造函数注入
        /// 
        /// 
        /// 
        /// 
        /// 
        public OrderCommandHandler(IOrderRepository OrderRepository,
            IMediatorHandler bus
        ) : base( bus)
        {
            _OrderRepository = OrderRepository;
            Bus = bus;
        }


        // RegisterOrderCommand命令的处理程序
        // 整个命令处理程序的核心都在这里
        // 不仅包括命令验证的收集,持久化,还有领域事件和通知的添加
        public Task Handle(RegisterOrderCommand message, CancellationToken cancellationToken)
        {

            // 实例化领域模型,这里才真正的用到了领域模型
            // 注意这里是通过构造函数方法实现

            var Order = new Order(Guid.NewGuid(), message.Name, message.Address, message.OrderItem);

            //返回错误
            if (Order.Name.Equals("Err"))
            {
                Bus.RaiseEvent(new DomainNotification("", "订单名为Err"));
                return Task.FromResult(new Unit());
            }


            // 持久化
            _OrderRepository.Add(Order);


            if (_OrderRepository.SaveChanges() > 0)
            {
                Bus.RaiseEvent(new RegisterOrderEvent());
            }

            Bus.RaiseEvent(new DomainNotification("", "Register成功") );

            return Task.FromResult(new Unit());

        }


        // 手动回收
        public void Dispose()
        {
            _OrderRepository.Dispose();
        }
    }

然后在StartUp的依赖注入中注入:

    public class NativeInjectorBootStrapper
    {
        public static void RegisterServices(IServiceCollection services)
        {

            // ASP.NET HttpContext dependency
            services.AddSingleton();

            // 注入 应用层Application
            services.AddScoped();

            //命令总线Domain Bus(Mediator)
            services.AddScoped();


            // 领域层 - 领域命令
            // 将命令模型和命令处理程序匹配
            services.AddScoped, OrderCommandHandler>();

            // 领域事件
            services.AddScoped, OrderEventHandler>();

            // 领域通知
            services.AddScoped, DomainNotificationHandler>();


            // 领域层 - Memory
            services.AddSingleton(factory =>
            {
                var cache = new MemoryCache(new MemoryCacheOptions());
                return cache;
            });

            // 注入 基础设施层 - 数据层
            services.AddScoped();
            services.AddScoped();
        }
    }

然后我们就可以在应用层,这样调用:

public class OrderAppService : IOrderAppService
    {
        // 用来进行DTO
        private readonly IMapper _mapper;
        // 中介者 总线
        private readonly IMediatorHandler Bus;

        public OrderAppService(
            IOrderRepository OrderRepository,
            IMapper mapper, IMediatorHandler bus
        )
        {
            _mapper = mapper;
            Bus = bus;
        }

        public void Register(OrderViewModel OrderViewModel)
        {
            var registerCommand = _mapper.Map(OrderViewModel);
            Bus.SendCommand(registerCommand);
        }
    }

这样子请求最后被转发去哪个处理程序,就可以在依赖注入中配置了。

总结

到这里为止,应用层,服务层,基础设施层是绝对内聚的。在开发过程中,各层只需要考虑自己要实现的功能的,不会受其他层的业务影响。

你可能感兴趣的:(DDD领域驱动设计——CQRS,MediatR)