当前位置: 代码迷 >> 驱动开发 >> DDD~领域事件应用篇(订单处理变得更明晰)
  详细解决方案

DDD~领域事件应用篇(订单处理变得更明晰)

热度:294   发布时间:2016-04-28 10:03:55.0
DDD~领域事件应用篇(订单处理变得更清晰)

回到目录

上一讲主要说了领域事件和领域总线,这并不是一个很容易理解的文章,所以本讲实例篇主要是为了补充上一讲的理论知识,本讲实例关注的是实际中的订单处理模块,我们知道,订单处理是电子商务的核心,往往在这里面,会有很多逻辑,在开发时,给我们带来了不少的难度,如何更好的分离关注点,是本讲的主题;本讲主要是修改订单状态后,为用户发通知为例,来以此更好的说一下领域事件在实际中的作用。

前言 领域事件使用的设计模式是发布/订阅模式,或者叫观察者模式。

一 订单的几种状态,在订单进入到其中一些状态时,需要通知用户

  public enum OrderStatus    {        /// <summary>        /// 表示销售订单的已创建状态 - 表明销售订单已被创建(未用)。        /// </summary>        [Description("用户下单")]        Created = 0,        /// <summary>        /// 订单被用户取消        /// </summary>        [Description("取消订单")]        Canceled,        /// <summary>        /// 表示销售订单的已付款状态 - 表明客户已向销售订单付款。        /// </summary>        [Description("用户付款")]        Paid,        /// <summary>        /// 表示销售订单的已发货状态。        /// </summary>        [Description("已经发货")]        Dispatched,        /// <summary>        /// 订单已经完成        /// </summary>        [Description("订单完成")]        Finished,    }

二 为需要通知的状态,创建领域事件数据源(原来在.net事件里,它一般是EventArgs对象)

    /// <summary>    /// 订单已经确认事件源    /// </summary>    public class Order_Confirm_Event : Project.Events.EventBase    {        public int OrderId { get; set; }        public string UserName { get; set; }    }
    /// <summary>    /// 订单已经付款事件缘    /// </summary>    public class Order_Paid_Event : Project.Events.EventBase    {        public int OrderId { get; set; }        public string UserName { get; set; }        public decimal TotalPrice { get; set; }    }
    /// <summary>    /// 订单已经发货事件源    /// </summary>    public class Order_Dispatch_Event : Project.Events.EventBase    {        public int OrderId { get; set; }        public string UserName { get; set; }    }

三 为这些事件源添加事件处理方式

我们一般理解为“行为”,一种事件源可以有多种事件行为,如一个订单付款事件源,它可以同时有电子邮件行为和短信行为;反之,一个事件处理方式也可以对应多个事件源,如一个电子邮件行为,它可以同时对应订单付款

和订单发货等事件源,所以,领域事件在项目中是非常灵活的。

    /// <summary>    /// 发Email的事件处理方式    /// 调用方法:EventBus.Instance.Subscribe<User_Depts_Event>(new SendEmailEventHandler())    ///      EventBus.Instance.Subscribe<User_Roles_Event>(new SendEmailEventHandler())    /// </summary>    public class SendEmail_EventHandler :       IEventHandler<Order_Confirm_Event>,        IEventHandler<Order_Dispatch_Event>,        IEventHandler<Order_Paid_Event>    {        static string prefix = "本消息来自电子邮件";        #region IEventHandler<Order_Confirm_Event> 成员        public void Handle(Order_Confirm_Event evt)        {            Logger.Core.LoggerFactory.Instance.Logger_Info(string.Format("尊敬的客户{0},你的订单已经确认,订单号{1},请尽快付款!({2})[{3}]"                , evt.UserName                , evt.OrderId                , evt.EventDate                , prefix));        }        #endregion        #region IEventHandler<Order_Dispatch_Event> 成员        public void Handle(Order_Dispatch_Event evt)        {            Logger.Core.LoggerFactory.Instance.Logger_Info(string.Format("尊敬的客户{0},你的订单已经发货,订单号{1},请注意查收!({2})[{3}]"                , evt.UserName                , evt.OrderId                , evt.EventDate                , prefix));        }        #endregion        #region IEventHandler<Order_Paid_Event> 成员        public void Handle(Order_Paid_Event evt)        {            Logger.Core.LoggerFactory.Instance.Logger_Info(string.Format("尊敬的客户{0},你的订单已经成功付款,订单号{1},付款金额为{2}元,我们会尽快安排发货!({3})[{4}]"                 , evt.UserName                 , evt.OrderId                 , evt.TotalPrice                 , evt.EventDate                 , prefix));        }        #endregion    }
    /// <summary>    /// 发短信的事件处理方式    /// </summary>    public class SendSMS_EventHandler :         IEventHandler<Order_Confirm_Event>,         IEventHandler<Order_Dispatch_Event>,         IEventHandler<Order_Paid_Event>    {        static string prefix = "本消息来自短信";        #region IEventHandler<Order_Confirm_Event> 成员        public void Handle(Order_Confirm_Event evt)        {            Logger.Core.LoggerFactory.Instance.Logger_Info(string.Format("尊敬的客户{0},你的订单已经确认,订单号{1},请尽快付款!({2})[{3}]"                , evt.UserName                , evt.OrderId                , evt.EventDate                , prefix));        }        #endregion        #region IEventHandler<Order_Dispatch_Event> 成员        public void Handle(Order_Dispatch_Event evt)        {            Logger.Core.LoggerFactory.Instance.Logger_Info(string.Format("尊敬的客户{0},你的订单已经发货,订单号{1},请注意查收!({2})[{3}]"                , evt.UserName                , evt.OrderId                , evt.EventDate                , prefix));        }        #endregion        #region IEventHandler<Order_Paid_Event> 成员        public void Handle(Order_Paid_Event evt)        {            Logger.Core.LoggerFactory.Instance.Logger_Info(string.Format("尊敬的客户{0},你的订单已经成功付款,订单号{1},付款金额为{2}元,我们会尽快安排发货!({3})[{4}]"                 , evt.UserName                 , evt.OrderId                 , evt.TotalPrice                 , evt.EventDate                 , prefix));        }        #endregion    }

四 订阅领域事件

我们需要有一个入口来订阅领域事件,如订阅订单确认事件,订单付款事件等,简单的做法是在global里统一注册,而标准合理的作法,我觉得应该在订单controller的静态构造方法里去注册它,因为只有用户操作订单相关模块时,才可能触

发订单这些事件!

     /// <summary>        /// 类的构造方法,处理与具体实例无法的东西        /// </summary>        static OrderController()        {            #region 订阅相关领域事件            #region 订阅发email处理方式            EventBus.Instance.Subscribe<Order_Confirm_Event>(new SendEmail_EventHandler());            EventBus.Instance.Subscribe<Order_Dispatch_Event>(new SendEmail_EventHandler());            EventBus.Instance.Subscribe<Order_Paid_Event>(new SendEmail_EventHandler());            #endregion            #region 订阅发短信的处理方式            EventBus.Instance.Subscribe<Order_Confirm_Event>(new SendSMS_EventHandler());            EventBus.Instance.Subscribe<Order_Dispatch_Event>(new SendSMS_EventHandler());            EventBus.Instance.Subscribe<Order_Paid_Event>(new SendSMS_EventHandler());            #endregion            #endregion        }

我们看到了,每种订单事件源都被注册了电子邮件和短信两种事件处理方式,这在对应的事件被触发(发布,publish)之后,它们会被执行。

六 触发具体事件

具体的事件在具体的方法里,独立的去触发,一般它会在业务逻辑层完成

     /// <summary>        /// 用户下单:create        /// </summary>        /// <param name="entity"></param>        public void DoOrder(Order_Info entity)        {            entity.OrderStatus = (int)OrderStatus.Created;            _orderRepository.Insert(entity);            entity.Order_Detail.ToList().ForEach(j => j.OrderId = entity.Id);            _orderDetailRepository.Insert(entity.Order_Detail);            EventBus.Instance.Publish(new Order_Confirm_Event { OrderId = entity.Id, UserName = entity.UserName });        }        /// <summary>        /// 订单付款:paid        /// </summary>        /// <param name="entity"></param>        public void PaidOrder(Order_Info entity)        {            entity.OrderStatus = (int)OrderStatus.Paid;            _orderRepository.Update(entity);            EventBus.Instance.Publish(new Order_Paid_Event { OrderId = entity.Id, TotalPrice = entity.TotalPrice, UserName = entity.UserName });        }        /// <summary>        /// 订单发货:dispatch        /// </summary>        /// <param name="entity"></param>        public void DispatchOrder(Order_Info entity)        {            entity.OrderStatus = (int)OrderStatus.Dispatched;            _orderRepository.Update(entity);            EventBus.Instance.Publish(new Order_Dispatch_Event { OrderId = entity.Id, UserName = entity.UserName });        }

八 最后,我们看一下处理处理页面和结果的截图

生成的日志

生成的日志内容,类似于用户上到的电子邮件或者短信的内容

怎么样,看完这个例子相信您对领域事件有了更清晰的认识了吧,呵呵!

回到目录

4楼舍弃众神
不错,正好让我理解了以前不明白的地方
Re: 张占岭
@舍弃众神,呵呵,好好学习,天天向上!
3楼l3oz
你所描述的领域事件是在Application层中触发的,但我看来这其实是应用事件,领域事件应该会带有领域对象状态的改变,且应在领域层内触发。
2楼netfocus
domain event应该是在领域内产生,一般又聚合根产生,同时伴随着聚合根的状态变化。楼主的这个事件,我也觉得是application event。,另一点建议,为啥你的代码都带下划线的?看起来好难受,比如:Order_Detail,Order_Dispatch_Event 。C#的命名规范中,基本使用camel, Pascal命名法。
Re: 张占岭
@netfocus,噢,写习惯了,看来回头我要修正规点了,呵呵,谢谢建议!
1楼netfocus
另外,感觉你这个不是领域驱动设计,因为我看到你保存订单时,先保存订单,再保存订单明细,分两步持久化。而如果是DDD的做法,那订单我觉得应该会聚合订单明细,然后保存时只保存订单聚合根即可。
Re: 张占岭
@netfocus,恩,向您学习的还不够,目前正在研究聚合根,实体和值对象,呵呵,目前我的项目里只引入了缓存,事件和规约,呵呵,谢谢大师的评论。
Re: 超越火炮兰
@netfocus,你说得对,不是ddd的设计也应该保证2个事件在同一个事物内处理. 这是很基础的. 不能扯一个ddd的大旗,就可以违背基本的设计原则.
  相关解决方案