DDD领域驱动设计——领域事件Event

前言

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

在前面的文章里,我们会发现一个问题。
通过MediatR发送处理的请求,返回值都是void,这等于说变成了发后即忘的状态。在正常开发中,这是不被允许的,至少系统出现异常,要返回结果出去。
而通知的问题,可以归入领域事件Event。

领域事件

以我们的例子,假设领导加了一个需求:创建订单后,需要给用户发送通知短信。
可能你会直接在处理完订单后,直接加上一段SendMessage的代码,但是问题来了,发短信跟处理订单有关系嘛?
发送短信是创建订单必须的功能嘛,显然不是。那么如果以后频繁加入类似短信,邮件或者其他与当前业务无关的代码,那么项目迟早面目全非。所以我们加入了领域事件
领域事件:对于业务来说,不是必定的,可以变化的业务,是领域事件。
事件抽象类,和实现

    /// <summary>
    /// 事件模型 抽象基类,继承 INotification
    /// 也就是说,拥有中介者模式中的 发布/订阅模式
    /// 同时继承了Messgae 也就是继承了 请求/响应模式
    /// </summary>
    public abstract class Event : INotification
    {
        // 时间戳
        public DateTime Timestamp { get; private set; }

        // 每一个事件都是有状态的
        protected Event()
        {
            Timestamp = DateTime.Now;
        }
    }

    public class RegisterOrderEvent : Event
    {

    }

    /// <summary>
    /// 领域通知模型,用来获取当前总线中出现的通知信息
    /// 继承自领域事件和 INotification(也就意味着可以拥有中介的发布/订阅模式)
    /// </summary>
    public class DomainNotification : Event
    {
        // 标识
        public Guid DomainNotificationId { get; private set; }
        // 键(可以根据这个key,获取当前key下的全部通知信息)
        // 这个我们在事件源和事件回溯的时候会用到,伏笔
        public string Key { get; private set; }
        // 值(与key对应)
        public string Value { get; private set; }
        // 版本信息
        public int Version { get; private set; }

        public DomainNotification(string key, string value)
        {
            DomainNotificationId = Guid.NewGuid();
            Version = 1;
            Key = key;
            Value = value;
        }
    }

我们用同样的方式写事件的处理程序,实现INotificationHandler<RegisterOrderEvent>和INotificationHandler<DomainNotification>

    public class OrderEventHandler : INotificationHandler<RegisterOrderEvent>
    {
        public Task Handle(RegisterOrderEvent notification, CancellationToken cancellationToken)
        {
            // 恭喜您,注册成功,欢迎加入我们。

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// 领域通知处理程序,把所有的通知信息放到事件总线中
    /// 继承 INotificationHandler<T>
    /// </summary>
    public class DomainNotificationHandler : INotificationHandler<DomainNotification>
    {
        // 通知信息列表
        private List<DomainNotification> _notifications;

        // 每次访问该处理程序的时候,实例化一个空集合
        public DomainNotificationHandler()
        {
            _notifications = new List<DomainNotification>();
        }

        // 处理方法,把全部的通知信息,添加到内存里
        public Task Handle(DomainNotification message, CancellationToken cancellationToken)
        {
            _notifications.Add(message);
            return Task.CompletedTask;
        }

        // 获取当前生命周期内的全部通知信息
        public virtual List<DomainNotification> GetNotifications()
        {
            return _notifications;
        }

        // 判断在当前总线对象周期中,是否存在通知信息
        public virtual bool HasNotifications()
        {
            return GetNotifications().Any();
        }

        // 手动回收(清空通知)
        public void Dispose()
        {
            _notifications = new List<DomainNotification>();
        }
    }

依赖注入:

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

            // ASP.NET HttpContext dependency
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            // ASP.NET Authorization Polices
            //services.AddSingleton<IAuthorizationHandler, ClaimsRequirementHandler>();

            // 注入 应用层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>();
        }
    }

领域层处理完调用事件:

    /// <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();
        }
    }

在UI层获取消息:

    [ApiController]
    [Route("api/[controller]")]
    public class OrderController : ControllerBase
    {
        private readonly IOrderAppService _OrderAppService;

        private readonly DomainNotificationHandler _notification;


        public OrderController(IOrderAppService OrderAppService, INotificationHandler<DomainNotification> notification)
        {
            _OrderAppService = OrderAppService;
            _notification = (DomainNotificationHandler) notification;
        }

        // POST: Order/Create
        // 方法
        [HttpPost("Create")]
        //[ValidateAntiForgeryToken]
        public object Create([FromBody]OrderViewModel OrderViewModel)
        {
            // 视图模型验证
            if (!ModelState.IsValid)
                return false;

            // 这里为了测试,手动赋值items
            OrderViewModel.Items = new List<OrderItemViewModel>() {
                    new OrderItemViewModel (){
                        Name="详情"+DateTime.Now
                    }
                };

            // 执行添加方法
            _OrderAppService.Register(OrderViewModel);

            if (_notification.HasNotifications())
            {
                return _notification.GetNotifications();
            }

            return true;
        }
    }

总结:

到这里为止,这个系列就算完成了。此系列重点讲解DDD和微服务的思想,其他部分都以最简单的方式实现。请大佬们不要喷我!我已经很努力的写了!呜呜呜呜呜!

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

推荐阅读更多精彩内容