EventBus InMemory 的实践基于eShopOnContainers (二)
前言
最近在工作中遇到了一个需求,会用到EventBus,正好看到eShopOnContainers上有相关的实例,去研究了研究。下面来分享一下用EventBus 来改造一下我们上篇Event发布与实践 中所用的Event。
在上一篇中讲到Event在发布与订阅模式中的一些实例,接下来实践一下通过把上面的例子改造成EventBus来加深理解。也感谢参考资料中大佬前辈们的思想和精华。
理解事件及其本质
我们所构建的一个场景是这样的:有个CarManager类,其中有个发车了的事件名为CarNotification, 有司机Driver和乘客Passenger这两个类。分别订阅了 发车事件, 这里司机和乘客收到通知后,然后简单的处理,仅仅打印出 司机和乘客的姓名信息。
在上面的场景中,我们可以知道,事件源,事件处理各是什么。
事件源:司机 或者 乘客 类。
事件处理: 打印出 司机或 乘客的信息。
大概花了一张巨丑的图,下面:
开始抽象事件源
接下来,我们可以开始抽象起来了。首先是 事件源:
定义一个所有事件源的父类,名为 EventData: 所有的事件源都需要继承该类
public class EventData{
public Guid Id{ get; }
public DateTime CreationDate{ get; }
public EventData(){
Id = Guid.NewGuid();
CreationDate = DateTime.Now;
}
}
然后我们就可以把我们之前的Driver 和Passenger 用我们定义的事件源来改造一下:
public class CarNotificationEventData : EventData{
private string _driverName;
private string _passengerName;
public CarNotification(string driverName, string passengerName){
_driverName = driverName;
_passengerName = passengerName;
}
public string Driver{ get{ return _driverName; } }
public string Passenger{ get{return _passengerName; } }
}
接下来就是抽象 事件处理 了,我们在此定义一个名为 IEventDataHandler 的接口:
public interface IEventHandler<in TEventData> : IEventHandler where TEventData : EventData{
Task Handle(TEventData eventData);
}
public interface IEventHandler{
}
- 定义了一个泛型接口,参数必须继承自 EventData 类型。
- 一个方法 Handle 方法,接收的参数也为 EventData 类型。
查看eShopOnContainer的源码时,上面那个为啥定义,继承一个空接口IEventHandler ,当时有点没搞明白,后来继续去看了看源码,发现了下面这段:
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
大胆猜测一下,应该是反射时会用到,所以定义了一个空接口
那么我们根据上面的接口,简单改造一下我们的事件处理代码:
public class DriverHandler : IEventHandler<CarNotificationEventData>{
public void Handle (CarNotificationEventData carNotificationEventData) {
Console.WriteLine ("Driver Hanlder---------");
Console.WriteLine (carNotificationEventData.Driver + "\n" + carNotificationEventData.Passenger +
"\n" + carNotificationEventData.EventDate);
}
}
偷了个懒,简单了打印了以下事件源中的信息,当然生产环境中,你就根据自己的业务逻辑来进行处理。
当然,改了事件源和事件处理,当然也需要重新对 我们当初 事件 以及 委托的定义。
//修改委托的定义:
public delegate void CarEventDataHandler(CarNotificationEventData eventData);
//修改事件的定义:
public event CarEventDataHandler CarNotification;
实现到上面那步,其实我们还只是刚刚开始,因为你会发现,我们只仅仅把那些事件源和事件处理抽线了出来,在每个事件处理程序中,我们可能还需要通过
但并没有真正上的做到用事件总线来实现。
为了更好的实现下面的事件总线,我们把委托也单独定义到一个class 中:
namespace EventDemo{
public delegate void CarNotificationDelegate(CatNotificationEventData eventData);
}
实现事件总线
根据 eShopOnContainers上的源码 IEventBusSubscription,我们来实现一个基于InMemory(存在Dictionary 中)的事件总线。
首先想到的 发布与订阅模式,所以呢,这个事件总线里面一定要有可以订阅和移除订阅的方法,还有来个额外的判断当前事件总线是否为空,当然还有一个就是 事件,接下来我们就定义一个 IEventBusSubscription:
public interface IEventBusSubscriptionsManager {
bool IsEmpty { get; }
event CarNotificationDelegate OnEventRemoved;
void AddSubscription<T, TH> () where T : CarNotificationEventData where TH : IEventHandler<T>;
void RemoveSubscription<T, TH> (T eventBusData) where T : CarNotificationEventData where TH : IEventHandler<T>;
bool HasSubscriptionsForEvent<T> () where T : CarNotificationEventData;
bool HasSubscriptionsForEvent (string eventName);
}
就简单点,一个订阅方法,一个移除订阅方法。还有一个事件 OnEventRemoved
接下来就是实现了,eShopOnContainer 上面有好多个版本,我在实践中,试了试 InMemory 和 RabbitMQ 的。因为十分贴合我的业务场景,本篇先介绍一下InMemory的实现,因为对RabbitMQ理解的还不是很深入, RabbitMQ版本的后续博文中跟进。
然后我们建立一个InMemoryEventBusSubscriptionsManager 继承至IEventBusSubscripionsManager
public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager {
private readonly Dictionary<string, List<Type>> _handler; //Type is HandlerType
private readonly List<Type> _eventTypes;
public event CarNotificationDelegate OnEventRemoved;
private readonly IServiceProvider _service;
public InMemoryEventBusSubscriptionsManager (IServiceCollection service) {
_handler = new Dictionary<string, List<Type>> ();
_eventTypes = new List<Type> ();
_service = service.BuildServiceProvider ();
OnEventRemoved += BeiginProcess;
}
public bool IsEmpty => !_handler.Keys.Any ();
public void AddSubscription<T, TH> ()
where T : CarNotificationEventData
where TH : IEventHandler<T> {
var eventName = GetEventKey<T> ();
if (!HasSubscriptionsForEvent<T> ()) {
_handler.Add (eventName, new List<Type> ());
}
if (_handler[eventName].Any (t => t == typeof (TH))) {
throw new ArgumentException (
$"Handler Type {typeof(TH).Name} already registered");
}
_handler[eventName].Add (typeof (TH));
_eventTypes.Add (typeof (T));
}
public void RemoveSubscription<T, TH> (T eventData)
where T : CarNotificationEventData
where TH : IEventHandler<T> {
var handlerToRemove = FindSubscriptionToRemove<T, TH> ();
DoRemoveHandler (eventData, handlerToRemove);
}
private void DoRemoveHandler (CarNotificationEventData eventData, Type subsToRemove) {
if (subsToRemove != null) {
var eventName = eventData.GetType ().Name;
_handler[eventName].Remove (subsToRemove);
if (!_handler[eventName].Any ()) {
_handler.Remove (eventName);
var eventType = _eventTypes.SingleOrDefault (e => e == eventName.GetType ());
if (eventType != null) {
_eventTypes.Remove (eventType);
}
RaiseOnEventRemoved (eventData);
}
}
}
private void RaiseOnEventRemoved (CarNotificationEventData eventData) {
var handler = OnEventRemoved;
if (handler != null) {
OnEventRemoved (eventData);
}
}
private Type FindSubscriptionToRemove<T, TH> ()
where T : CarNotificationEventData
where TH : IEventHandler<T> {
var eventName = GetEventKey<T> ();
return DoFindSubscriptionToRemove (eventName, typeof (TH));
}
private Type DoFindSubscriptionToRemove (string eventName, Type handlerType) {
if (!HasSubscriptionsForEvent (eventName)) {
return null;
}
return _handler[eventName].SingleOrDefault (s => s == handlerType);
}
public bool HasSubscriptionsForEvent<T> () where T : CarNotificationEventData {
var keyName = GetEventKey<T> ();
return _handler.ContainsKey (keyName);
}
public bool HasSubscriptionsForEvent (string eventName) => _handler.ContainsKey (eventName);
public string GetEventKey<T> () {
return typeof (T).Name;
}
public Type GetEventTypeByName (string eventName) => _eventTypes.SingleOrDefault (t => t.Name == eventName);
public async void BeiginProcess (CarNotificationEventData eventData) {
await Process (eventData);
}
private async Task Process (CarNotificationEventData eventBusData) {
var eventName = eventBusData.GetType ().Name;
if (HasSubscriptionsForEvent (eventName)) {
var subscriptions = _handler[eventName];
foreach (var subscription in subscriptions) {
var eventType = GetEventTypeByName (eventName);
var handler = _service.GetService (subscription);
var concreteType = typeof (IEventHandler<>).MakeGenericType (eventType);
await (Task) concreteType.GetMethod ("EventHandle").Invoke (handler, new object[] { eventBusData });
}
}
}
}
我稍微改动了以下地方的代码,使我的实例更加符合场景上的运行,
- eShopOnContainer 里面使用了 Autofac 来运用DI,我直接使用了 .net -core 中自带的 IServiceProvider来替代,感觉更加方便
- 直接对外显示一个public 的BeginProcess 来引发事件,主要为了演示方便。
既然EventBus 都写好,我们可以开始运行了,
//...
public static void Main (string[] args) {
#region EventBusRegister Demo
var serviceProvider = new ServiceCollection ()
.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager> ()
.AddTransient<DriverHandler> ();
var eventBus = new InMemoryEventBusSubscriptionsManager (serviceProvider);
RegisterEventBus (eventBus);
CarNotificationEventData carNotificationEventData = new CarNotificationEventData ("Robert 1", "Passenger 1");
eventBus.BeiginProcess (carNotificationEventData);
#endregion
}
//...
dotnet run 运行一下,得到如下结果:
这样就算大功告成了。接下来会写一篇结合RabbitMQ 的EventBus ,就更加符合生产环境的情景了。文中如果解释的不到位处,欢迎评论中指出,一起探讨。
参考资料
EventBus InMemory 的实践基于eShopOnContainers (二)的更多相关文章
- 《CMake实践》笔记二:INSTALL/CMAKE_INSTALL_PREFIX
<CMake实践>笔记一:PROJECT/MESSAGE/ADD_EXECUTABLE <CMake实践>笔记二:INSTALL/CMAKE_INSTALL_PREFIX &l ...
- 【转】RHadoop实践系列之二:RHadoop安装与使用
RHadoop实践系列之二:RHadoop安装与使用 RHadoop实践系列文章,包含了R语言与Hadoop结合进行海量数据分析.Hadoop主要用来存储海量数据,R语言完成MapReduce 算法, ...
- 机器学习算法与Python实践之(二)支持向量机(SVM)初级
机器学习算法与Python实践之(二)支持向量机(SVM)初级 机器学习算法与Python实践之(二)支持向量机(SVM)初级 zouxy09@qq.com http://blog.csdn.net/ ...
- 菜鸟Scrum敏捷实践系列(二)用户故事验收
菜鸟Scrum敏捷实践系列索引 菜鸟Scrum敏捷实践系列(一)用户故事概念 菜鸟Scrum敏捷实践系列(二)用户故事验收 菜鸟Scrum敏捷实践系列(三)用户故事的组织---功能架构的规划 一.用户 ...
- 基于Dapper二次封装了一个易用的ORM工具类:SqlDapperUtil
基于Dapper二次封装了一个易用的ORM工具类:SqlDapperUtil,把日常能用到的各种CRUD都进行了简化封装,让普通程序员只需关注业务即可,因为非常简单,故直接贴源代码,大家若需使用可以直 ...
- 20155326 第12周课堂实践总结(二)String类和Arrays类的学习
20155326 第12周课堂实践总结(二)String类和Arrays类的学习 实践二 Arrays和String单元测试 实践题目 在IDEA中以TDD的方式对String类和Arrays类进行学 ...
- 基于EasyNVR二次开发实现自己的摄像机IPC/NVR无插件化直播解决方案
在之前的博客中<基于EasyNVR实现RTSP/Onvif监控摄像头Web无插件化直播监控>,我们已经比较多的描述EasyNVR所实现的功能,这些也在方案地址:http://www.eas ...
- 《CMake实践》笔记二:INSTALL/CMAKE_INSTALL_PREFIX【转】
本文转载自:http://www.cnblogs.com/52php/p/5681751.html 四.更好一点的Hello World 没有最好,只有更好 从本小节开始,后面所有的构建我们都将采用 ...
- 基于canvas二次贝塞尔曲线绘制鲜花
canvas中二次贝塞尔曲线参数说明: cp1x:控制点1横坐标 cp1y:控制点1纵坐标 x: 结束点1横坐标 y:结束点1纵坐标 cp2x:控制点2横坐标 cp2y:控制点2纵坐标 z:结束点2横 ...
随机推荐
- 假设检验(Hypothesis Testing)
假设检验(Hypothesis Testing) 1. 什么是假设检验呢? 假设检验又称为统计假设检验,是数理统计中根据一定假设条件由样本推断总体的一种方法. 什么意思呢,举个生活中的例子:买橘子(借 ...
- Beta 第四天
今天遇到的困难: 百度位置假死的问题研究发现并不是源于代码的问题,而是直接运行在主线程中会出现诸多问题 Fragment碎片刷新时总产生的固定位置的问题未果 今天完成的任务: 陈甘霖:修复了部分Bug ...
- C语言--总结报告
1.当初你是如何做出选择计算机专业的决定的? 经过一个学期,你的看法改变了么,为什么? 你觉得计算机是你喜欢的领域吗,它是你擅长的领域吗? 为什么? 当初填报志愿我是有很明确的专业方向的,就是IT类的 ...
- 团队作业4——第一次项目冲刺(Alpha版本) Day 1
小队@JMUZJB-集美震惊部 一.Daily Scrum Meeting照片 二.Burndown Chart 燃尽图 三.项目进展 1.界面 屏幕开发中,原型设计完毕. 2.服务器 服务器由学校提 ...
- android 自定义ScrollView实现背景图片伸缩(阻尼效果)
android 自定义ScrollView实现强调内容背景图片伸缩(仿多米,qq空间背景的刷新) 看到一篇文章,自己更改了一下bug: 原文地址:http://www.aiuxian.com/arti ...
- JUnit单元测试遇到的问题及解决思路
JUnit是Java单元测试框架,我们在对开发的系统进行单元测试的时候,也遇到了如何测试多个测试用例的问题. 背景:我们的所有测试用例都保存在Excel文件中,该文件包含测试用例和预期输出.我们希望 ...
- jstree的简单用法
一般我们用jstree主要实现树的形成,并且夹杂的邮件增删重命名刷新的功能 下面是我在项目中的运用,采用的是异步加载 $('#sensor_ul').data('jstree', false).emp ...
- SpringMVC源码情操陶冶#task-executor解析器
承接Spring源码情操陶冶-自定义节点的解析.线程池是jdk的一个很重要的概念,在很多的场景都会应用到,多用于处理多任务的并发处理,此处借由spring整合jdk的cocurrent包的方式来进行深 ...
- 微信浏览器的页面在PC端访问
微信浏览器的页面在PC端访问: 普通的在微信浏览器看的页面如果不在php代码中解析一下,然后复制链接在PC打开就出现无法访问,因为它复制的地址是: https://open.weixin.qq.com ...
- 微信号的openid的深入理解
header('Location:https://open.weixin.qq.com/connect/oauth2/authorize?appid='.$this->appid.'&r ...

