前言
最近一直奔波于面试,面了几家公司的研发。有让我受益颇多的面试经验,也有让我感觉浪费时间的面试经历~
因为疫情原因,最近宅在家里也没事,就想着使用Redis配合事件总线去实现下具体的业务。
一个简单的电商,有几个重要的需求点
商品下单后TODO
订单支付后TODO
技术思路
这里用控制台实现上面的业务功能外,自行编写一个基于C#反射特性的事件总线,方便具体业务事件的后续扩展,比如订单支付后后续还要加会员消息推送啥的。使用Redis的发布订阅模式对事件处理进行异步化,提升执行性能。
所以最终技术架构就是 事件总线+Redis发布订阅。
这里先不急着将上面的订单、支付、会员 等进行建模。先将事件总线的架子搭好。首先需要理解事件总线在业务系统的目的是什么。
事件总线存在目的最重要的就是解耦 。我们需要实现的效果就是针对指定事件源对象触发事件后,但凡注册了该事件参数的事件处理类则开始执行相关代码。
下图可以看出我们的事件处理类均需要引用事件参数,所有事件处理类都是基于对事件参数处理的需求上来的。
但是!并不是意味创建了事件处理类就一定会去执行!能否执行除了取决于事件源的触发外就是必须有一层注册(也可称映射)。
在WinForm程序里处处可见事件的绑定,如 this.button1.OnClick+=button1OnClick;
那么在这里我将绑定事件放置到一个字典里。C#的字典Dictionary是个key value的键值对数据集合,键和值都可以是任意数据类型。
我们可以将事件处理类EventHandle和事件参数EventData作为键和值存储到字典里。在事件源触发时根据EventData反向找出所有的EventHandle
思路就是这样,开始编码了。
定义事件参数接口,后续具体业务的事件参数接口均要继承它。
////// 事件参数接口 ///public interface IEventData { ////// 事件源对象 ///object Source { get; set; } ////////// 事件发生的数据 ///////TDataModel Data { get; set; } ////// 事件发生时间 ///DateTime Time { get; set; } }
需要一个事件处理接口,后续具体业务的事件处理接口均需要继承它
////// 事件实现接口 ///public interface IEventHandlewhere T : IEventData { ////// 处理等级 /// 方便事件总线触发时候可以有序的执行相应 //////int ExecuteLevel { get; } ////// 事件执行 //////事件参数void Execute(T eventData); }
现在已经将事件参数和事件处理都抽象出来了,接下来是要实现上面说的注册容器的实现了。
////// 事件仓库 ///public interface IEventStore { ////// 事件注册 //////事件实现对象///事件参数void EventRegister(Type handle, Type data); ////// 事件取消注册 //////事件实现对象void EventUnRegister(Type handle); ////// 获取事件处理对象 /////////Type GetEventHandle(Type data); ////// 根据事件参数获取事件处理集合 //////事件参数类型///事件参数///IEnumerableGetEventHandleList(TEventData data); }
实现上面的接口
////// 基于反射实现的事件仓储 /// 存储事件处理对象和事件参数 ///public class ReflectEventStore : IEventStore { private static DictionaryStoreLst; public ReflectEventStore() { StoreLst = new Dictionary(); } public void EventRegister(Type handle, Type data) { if (handle == null || data == null) throw new NullReferenceException(); if (StoreLst.Keys.Contains(handle)) throw new ArgumentException($"事件总线已注册类型为{nameof(handle)} !"); if (!StoreLst.TryAdd(handle, data)) throw new Exception($"注册{nameof(handle)}类型到事件总线失败!"); } public void EventUnRegister(Type handle) { if (handle == null) throw new NullReferenceException(); StoreLst.Remove(handle); } public Type GetEventHandle(Type data) { if (data == null) throw new NullReferenceException(); Type handle = StoreLst.FirstOrDefault(p => p.Value == data).Key; return handle; } public IEnumerableGetEventHandleList(TEventData data) { if (data == null) throw new NullReferenceException(); var items = StoreLst.Where(p => p.Value == data.GetType()) .Select(k => k.Key); return items; } }
根据上面代码可以看出来,我们存储到Dictionary内的是Type类型,GetEventHandleList方法最终获取的是一个List
实现EventBus
////// 事件总线服务 ///public class EventBus : ReflectEventStore { public void Trigger(TEventData data, SortType sort = SortType.Asc) where TEventData : IEventData { // 这里如需保证顺序执行则必须循环两次 - -.... var items = GetEventHandleList(data).ToList(); Dictionary
上面可以看到,我们的事件总线是支持对绑定的事件处理对象进行有序处理,需要依赖下面这个枚举
////// 排序类型 ///public enum SortType { ////// 升序 ///Asc = 1, ////// 降序 ///Desc = 2 }
好了,至此,我们的简易版的事件总线就出来了~ 接下来就是去建模、实现相应的事件参数和事件处理类了。
创建订单模型:
////// 订单模型 ///public class OrderModel { ////// 订单ID ///public Guid Id { get; set; } ////// 用户ID ///public Guid UserId { get; set; } ////// 订单创建时间 ///public DateTime CreateTime { get; set; } ////// 商品名称 ///public string ProductName { get; set; } ////// 购买数量 ///public int Number { get; set; } ////// 订单金额 ///public decimal Money { get; set; } }
创建订单下单事件参数
public interface IOrderCreateEventData : IEventData { ////// 订单信息 ///OrderModel Order { get; set; } } ////// 订单创建事件参数 ///public class OrderCreateEventData : IOrderCreateEventData { public OrderModel Order { get; set; } public object Source { get; set; } public DateTime Time { get; set; } }
OK~接下来就是实现我们上面需求上的那些功能了。
////// 订单创建事件之消息处理类 ///public class OrderCreateEventNotifyHandle : IEventHandle{ public int ExecuteLevel { get; private set; } public OrderCreateEventNotifyHandle() { Console.WriteLine($"创建OrderCreateEventNotifyHandle对象"); this.ExecuteLevel = 2; } public void Execute(IOrderCreateEventData eventData) { Thread.Sleep(1000); Console.WriteLine($"执行订单创建事件之消息推送!订单ID:{eventData.Order.Id.ToString()},商品名称:{eventData.Order.ProductName}"); } }
订单创建消息之锁定库存处理类
////// 订单创建事件 锁定库存 处理类 ///public class OrderCreateEventStockLockHandle : IEventHandle{ public int ExecuteLevel { get; private set; } public OrderCreateEventStockLockHandle() { Console.WriteLine($"创建OrderCreateEventStockLockHandle对象"); this.ExecuteLevel = 1; } public void Execute(IOrderCreateEventData eventData) { Thread.Sleep(1000); Console.WriteLine($"执行订单创建事件之库存锁定!订单ID:{eventData.Order.Id.ToString()},商品名称:{eventData.Order.ProductName}"); } }
OK~ 到main方法下开始执行订单创建相关代码。
static void Main(string[] args) { Guid userId = Guid.NewGuid(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); EventBus eventBus = new EventBus(); eventBus.EventRegister(typeof(OrderCreateEventNotifyHandle), typeof(OrderCreateEventData)); eventBus.EventRegister(typeof(OrderCreateEventStockLockHandle), typeof(OrderCreateEventData)); var order = new Order.OrderModel() { CreateTime = DateTime.Now, Id = Guid.NewGuid(), Money = (decimal)300.00, Number = 1, ProductName = "鲜花一束", UserId = userId }; Console.WriteLine($"模拟存储订单"); Thread.Sleep(1000); eventBus.Trigger(new OrderCreateEventData() { Order = order }); stopwatch.Stop(); Console.WriteLine($"下单总耗时:{stopwatch.ElapsedMilliseconds}毫秒"); Console.ReadLine(); }
至此,我们采取事件总线的方式成功将需求实现了,执行后结果如下:
可以看到我们的下单总耗时是3038毫秒,如您所见,我们解决了代码的耦合性但是没有解决代码的执行效率。
下一章,将我们的Redis的发布订阅模式再加入进来,看是否能改善我们的代码执行效率~~
源码在下一篇博客上提供下载地址(毕竟现在才完成一半~)
TOP