閑話多說
領域事件大叔感覺是最不好講的一篇文章,所以拖欠了很久,但最終還是在2015年年前(陰歷)把這個知識點講一下,事件這個東西早在C#1.0時代就有了,那時學起來也是一個費勁,什么是委托,哪個是事件,搞的大家是糊里糊涂,進入C#2.0時代后,大叔也買了一本書,對于delegate和event這兩個知識點看了至少有20幾遍,感覺稍微有點明白了,明白了其中的真諦和用意。
委托:方法的規范,方法的模板,可以代表一類方法的集合
事件:委托的實例,事件在使用之前需要為它賦值,當然賦的就是一個方法;事件可以注冊和取消,當你注冊一個事件之后,在事件被觸發后,被注冊的方法將會被執行,這一般被稱為“方法的回調”,在設計模式里,又被稱為“pub/sub模式”,即發布/訂閱模式;在C#語言發展過程中,設計得為程序開發者考慮的很多,有些寫法得到了精簡,如Action和Func委托的出現之后,我們基本上告別了delegate,這對程序開發人員無疑是一件好事。
大叔框架中的事件
在大叔框架里,事件是常客,比如在早期的倉儲代碼里,你可以傳遞一個Action<string>的委托,來進行日志的記錄,這種方法在IoC出現后,被大叔屏蔽了,原因不在這里說了,還有就是在N層架構里,WEB層與BLL層進行通訊時,WEB層通過HttpClient請求第三方的API獲取數據,而BLL層的方法需要用到這個第三方API的返回值,而在BLL層直接訪問HTTP顯然是不合適的,所以,在WEB層到BLL層的方法參數設計時,需要有一個委托來接改從WEB層回調的方法返回值,這種代碼一般稱為“方法回調”。
web層向BLL層傳入一個委托
var entity = rechargeService.RechargeAuto( task, beforeTime, out result, (studentid, money) => { //代碼 });
BLL層接改這個委托的返回值,代碼在調用bll層這個方法時,首先會回調web層的http的方法
public Task_xuexiba_Recharge RechargeAuto( Task_Info task, DateTime beforeTime, out bool result, Func<int, decimal, RechargeXuexibaDTO> api) { //代碼 }
var apiEntity = api(task.Task_ParametersForXuexibaRecharge.StudentID, task.Task_ParametersForXuexibaRecharge.Money);
Lind.DDD框架里的領域事件
事件源后綴:Event
事件處理方法后綴:EventHandler
領域事件一般出現個領域實體里,在實體被建立時,會訂閱和自己有關的事件,每個事件都有一個或者多個事件處理方法,事件處理方法可以進行數據庫操作,或者網絡和文件的操作,如發通知,寫文件等,所以有時候我們的事件需要設計成異步的事件。
程序中的事件事件
#region 領域模型 public class Order { public Order() { Lind.DDD.Events.EventBus.Instance.Subscribe(new OrderInsertEventHandler()); Lind.DDD.Events.EventBus.Instance.Subscribe<OrderPaid>(new OrderUpdateEventHandler()); } public System.Guid Id { get; set; } public System.Guid UserId { get; set; } public string UserName { get; set; } public decimal TotalFee { get; set; } /// <summary> /// 用戶提交并確認訂單 /// </summary> public void ComfirmOrder() { //事件發布 Lind.DDD.Events.EventBus.Instance.Publish(new OrderConfirm { TotalFee = TotalFee, UserName = UserName, UserId = UserId, }); } } #endregion
下面是領域事件源
/// <summary> /// 訂單被確認的事件源 /// </summary> public class OrderConfirm : Lind.DDD.Events.IEvent { public override string ToString() { return "訂單已經確認"; } /// <summary> /// 訂單總金額 /// </summary> public decimal TotalFee { get; set; } /// <summary> /// 購買者ID /// </summary> public Guid UserId { get; set; } /// <summary> /// 購買者 /// </summary> public string UserName { get; set; } #region IEvent 成員 public Guid AggregateRoot { get { throw new NotImplementedException(); } } #endregion }
下面是領域事件的處理程序
/// <summary> /// 訂單被插入時的處理程序 /// </summary> public class OrderInsertEventHandler : Lind.DDD.Events.IEventHandler<Events.OrderConfirm> { #region IEventHandler<OrderSigned> 成員 public void Handle(Events.OrderConfirm evt) { //處理訂單確認的邏輯 var orderRepository = new Lind.DDD.Repositories.EF.EFRepository<Orders>(); orderRepository.SetDataContext(new testEntities()); orderRepository.Insert(new Orders { Id = Guid.NewGuid(), OrderStatus = 1, TotalFee = evt.TotalFee, UserId = evt.UserId, UserName = evt.UserName, }); } #endregion }
如果希望將自己的事件處理程序設計成異常的,即不阻塞當前線程的,可以讓它添加HandlesAsynchronouslyAttribute這個特性,如下面這個發送Email的處理程序就是一個異步的。
/// <summary> /// 發郵件功能[某個事件的行為] /// </summary> [HandlesAsynchronouslyAttribute] public class SendEmailEventHandler : IEventHandler<OrderEvent>, IEventHandler<UserEvent> { #region IEventHandler<OrderEvent> 成員 public void Handle(OrderEvent evt) { Console.WriteLine("生成和確認訂單{0}時發Email", evt.OrderId); } #endregion #region IEventHandler<UserEvent> 成員 public void Handle(UserEvent evt) { Console.WriteLine("建立用戶后發Email,用戶ID{0}", evt.UserId); } #endregion }
全局注冊所有事件處理程序
這個是看完ABP之后的想法,原理是把BIN下所有繼承了IEventHandler的類都自動注冊到事件總線中,然后在事件被觸發后,就自動執行你訂閱的方法了,在項目開發過程中,推薦使用這種方法,但需要注意的是,你的事件處理程序必須是顯示定義的,不能使用匿名的處理程序.
/// <summary> /// 全局統一注冊所有事件處理程序,實現了IEventHandlers的 /// </summary> public void SubscribeAll() { var types = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes() .Where(t => t.GetInterfaces().Contains(typeof(IEventHandlers)))) .Where(i => !Excepts.Contains(i.Name)) .ToArray(); foreach (var item in types) { if (!item.ContainsGenericParameters) { var en = Activator.CreateInstance(item); foreach (var t in item.GetInterfaces().Where(i => i.Name != "IEventHandlers")) { Subscribe(t, en); } } } }
感謝各位的閱讀!
文章列表