Lind.DDD.Events領域事件介紹
閒話多說
領域事件大叔感覺是最不好講的一篇文章,所以拖欠了很久,但最終還是在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 boolresult, 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); } } } }
感謝各位的閱讀!