事件总线可以从系统全局角度,提供一套发布订阅机制。使用过ABP框架开发,或者学习过DDD, 或者了解Rebus的同学,应该知道应用事件总线带来的诸多好处,而且它能给我们带来程序设计思想上的提高。
源码&Nuget
我通过学习一些开源组件库,并且从自身使用经验出发,从0设计了一个基于RabbitMq的分布式事件总线,大家可以到我的Azure Devops仓库查看源码。
并且我将其发布为了Nuget包,可以供大家安装使用
Install-Package MaH.EventBus -Version 0.7.0
我们先来看一个简单的应用:
//定义一个订单更新事件
namespace MaH.SimpleLocalEventBus.Console
{
public class UpdateOrderEvent
{
public DateTime Timestamp { get; set; }
public UpdateOrderEvent()
{
Timestamp = DateTime.Now;
}
}
}
//创建一个订单更新事件处理程序, 继承:IEventHandler<UpdateOrderEvent>
namespace MaH.SimpleLocalEventBus.Console
{
public class UpdateOrderHandler : IEventHandler<UpdateOrderEvent>
{
public Task InvokeAsync(UpdateOrderEvent eventData)
{
System.Console.WriteLine($"Update-{eventData.Timestamp}");
return Task.CompletedTask;
}
}
}
using System.Threading.Tasks;
//在EventBus中注册处理程序,当使用eventBus.PublishAsync()发布一个事件后,会根据事件类型自动触发处理程序
namespace MaH.SimpleLocalEventBus.Console
{
class Program
{
static async Task Main(string[] args)
{
ILocalEventBus eventBus = new LocalEventBus();
UpdateOrderHandler updateOrderHandler = new UpdateOrderHandler();
eventBus.Subscribe(updateOrderHandler);
await eventBus.PublishAsync(new UpdateOrderEvent());
System.Console.ReadKey();
}
}
}
本地事件总线
上面这个例子展示的是本地事件总线的一个简单应用,实现方式也非常基础,大家可以到MaH.EventBus 的Basic-1.0 分支查看具体实现。
public class LocalEventBus : ILocalEventBus
{
private ConcurrentDictionary<Type, List<IHandler>> handlers;
public LocalEventBus()
{
handlers = new ConcurrentDictionary<Type, List<IHandler>>();
}
public async Task PublishAsync<TEvent>(TEvent eventData) where TEvent : class
{
await TriggerEventAsync(GetOrAddHandlers(typeof(TEvent)), eventData);
}
public void Subscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : class
{
GetOrAddHandlers(typeof(TEvent)).Add(handler);
}
}
我们可以注意到,此处的实现是依靠注册具体的Handler实例对象来完成的,假如我们希望可以控制handler的生命周期,或者说,我们希望handler可以从IOC容器中获取,应该怎么做呢?
答案是引入工厂模式,用工厂方法来获取handler.
从IOC容器中获取Handler
我们可以把分支切换到 MaH.EventBus 的Basic-2.0来查看具体实现:
// LocalEventBus不再直接保存handler的引用,而是保存相应的IEventHandlerFactory
public class LocalEventBus : ILocalEventBus
{
private ConcurrentDictionary<Type, List<IEventHandlerFactory>> handlers;
public LocalEventBus()
{
handlers = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>();
}
public void Subscribe(Type eventType, IEventHandlerFactory handlerFactory)
{
GetOrAddHandlers(eventType).Add(handlerFactory);
}
}
namespace MaH.SimpleLocalEventBus
{
public interface IEventHandlerFactory
{
IHandler GetHandler();
}
}
我做了两种IEventHandlerFactory的实现,一种是SingletonEventHandlerFactory ,直接维持对于handler实例的引用:
public class SingletonEventHandlerFactory : IEventHandlerFactory
{
private readonly IHandler _handlerInstance;
public SingletonEventHandlerFactory(IHandler handlerInstance)
{
_handlerInstance = handlerInstance;
}
public IHandler GetHandler() => _handlerInstance;
}
另一种则是基于IOC容器,保存handler type 和对 IServiceScopeFactory的引用:
public class DefaultEventHandlerFactory : IEventHandlerFactory
{
private readonly Type _handlerType;
private readonly IServiceScopeFactory _serviceScopeFactory;
public DefaultEventHandlerFactory(Type handlerType, IServiceScopeFactory serviceScopeFactory)
{
_handlerType = handlerType;
_serviceScopeFactory = serviceScopeFactory;
}
public IHandler GetHandler()
{
var scope = _serviceScopeFactory.CreateScope();
return (IHandler) scope.ServiceProvider.GetRequiredService(_handlerType);
}
}
这样,我们就可以在使用了IOC技术的.NET 项目中使用事件总线啦!当然,一个优秀的事件总线框架也应该提供自动订阅机制,此处没有实现。
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<WeatherForecastHandler>();
services.AddSingleton<ILocalEventBus>(sp =>
{
var eventBus = new LocalEventBus();
eventBus.Subscribe(typeof(WeatherUpdateEvent), new DefaultEventHandlerFactory(typeof(WeatherForecastHandler), sp.GetRequiredService<IServiceScopeFactory>()));
return eventBus;
});
}
分布式事件总线
仅仅实现本地事件总线是不够的,因为在微服务设计中,往往牵涉到多个系统之间进行通信,比如支付系统发布一个订单支付通知,短信站点就需要根据订单信息来发送支付成功短信,等等。
因此,我们需要对目前的事件总线设计进行进一步升级,借助RabbitMQ,来实现一个分布式事件总线。当然,也可以使用Kafka, 数据库等.