C#&.NET 从0实现一个分布式事件总线(一)

事件总线可以从系统全局角度,提供一套发布订阅机制。使用过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, 数据库等.

猜你喜欢

转载自blog.csdn.net/qq_40404477/article/details/116763534