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

前言

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

CQRS

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

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

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

餐厅故事

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

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

MediatR,中介者模式

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

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


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

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

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

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

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

然后是处理程序

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

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


        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="uow"></param>
        /// <param name="bus"></param>
        /// <param name="cache"></param>
        public CommandHandler( IMediatorHandler bus)
        {
            _bus = bus;
        }
    }

订单处理程序,指定命令的处理程序,IRequestHandler<RegisterOrderCommand, Unit>

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

        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="OrderRepository"></param>
        /// <param name="uow"></param>
        /// <param name="bus"></param>
        /// <param name="cache"></param>
        public OrderCommandHandler(IOrderRepository OrderRepository,
            IMediatorHandler bus
        ) : base( bus)
        {
            _OrderRepository = OrderRepository;
            Bus = bus;
        }


        // RegisterOrderCommand命令的处理程序
        // 整个命令处理程序的核心都在这里
        // 不仅包括命令验证的收集,持久化,还有领域事件和通知的添加
        public Task<Unit> 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<IHttpContextAccessor, HttpContextAccessor>();

            // 注入 应用层Application
            services.AddScoped<IOrderAppService, OrderAppService>();

            //命令总线Domain Bus(Mediator)
            services.AddScoped<IMediatorHandler, InMemoryBus>();


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

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

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


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

            // 注入 基础设施层 - 数据层
            services.AddScoped<IOrderRepository, OrderRepository>();
            services.AddScoped<OrderContext>();
        }
    }

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

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<RegisterOrderCommand>(OrderViewModel);
            Bus.SendCommand(registerCommand);
        }
    }

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

总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容