Hands-create the wheel: the realization of a simple EventBus

Hands-create the wheel: the realization of a simple EventBus

Intro

EventBus event is a publish-subscribe model, we can easily achieve decoupling by EventBus, the well separated to initiate events and handling events, good decoupling. Microsoft's official sample project EShopOnContainers also use EventBus.

EventBus achieve here is a reference for the Microsoft eShopOnContainers project.

EventBus process flow:

event bus flow

Room service using micro EventBus achieve decoupling between systems:

event bus in microservice

With EventBus we can achieve a good service between between components, problems, and decoupled from mutual communication between systems.

At first EventBus feel almost the same thing and MQ are decoupled to achieve high performance through asynchronous processing. Later I saw the picture below be considered to understand why should the relationship between the EventBus and EventBus and MQ, EventBus is abstract, can be achieved with MQ EventBus.

eventbus implement

Why use EventBus

  1. Decoupling (decoupling between systems easy to achieve)
  2. High-performance scalable (each event is independent and does not change the simple objects, only new event needs to be saved, not other changes deletion)
  3. System Audit (each event is irrevocable, each event is traceable)
  4. ...

EventBus overall architecture:

  • IEventBase: All events must implement this interface, this interface defines an event unique id EventIdincidents and eventsEventAt

IEventBase

  • IEventHandler: Defines a Handlemethod to handle the corresponding event

IEventHandler

  • IEventStore: Processing to store all of the events, save the event IEventHandler, generally not directly operate, operated by EventBus subscriptions and unsubscribeEventStore

IEventStore

  • IEventBus: To publish / subscribe / unsubscribe events, one event and IEventHandlersaved to EventStore or removed from the EventStore

IEventBus

Examples of Use

来看一个使用示例,完整代码示例

internal class EventTest
{
    public static void MainTest()
    {
        var eventBus = DependencyResolver.Current.ResolveService<IEventBus>();
        eventBus.Subscribe<CounterEvent, CounterEventHandler1>();
        eventBus.Subscribe<CounterEvent, CounterEventHandler2>();
        eventBus.Subscribe<CounterEvent, DelegateEventHandler<CounterEvent>>();
        eventBus.Publish(new CounterEvent { Counter = 1 });

        eventBus.Unsubscribe<CounterEvent, CounterEventHandler1>();
        eventBus.Unsubscribe<CounterEvent, DelegateEventHandler<CounterEvent>>();
        eventBus.Publish(new CounterEvent { Counter = 2 });
    }
}

internal class CounterEvent : EventBase
{
    public int Counter { get; set; }
}

internal class CounterEventHandler1 : IEventHandler<CounterEvent>
{
    public Task Handle(CounterEvent @event)
    {
        LogHelper.GetLogger<CounterEventHandler1>().Info($"Event Info: {@event.ToJson()}, Handler Type:{GetType().FullName}");
        return Task.CompletedTask;
    }
}

internal class CounterEventHandler2 : IEventHandler<CounterEvent>
{
    public Task Handle(CounterEvent @event)
    {
        LogHelper.GetLogger<CounterEventHandler2>().Info($"Event Info: {@event.ToJson()}, Handler Type:{GetType().FullName}");
        return Task.CompletedTask;
    }
}

具体实现

EventStoreInMemory 实现:

EventStoreInMemory 是 IEventStore 将数据放在内存中的实现,使用了 ConcurrentDictionary 以及 HashSet 来尽可能的保证高效,具体实现代码如下:

public class EventStoreInMemory : IEventStore
{
    private readonly ConcurrentDictionary<string, HashSet<Type>> _eventHandlers = new ConcurrentDictionary<string, HashSet<Type>>();

    public bool AddSubscription<TEvent, TEventHandler>()
        where TEvent : IEventBase
        where TEventHandler : IEventHandler<TEvent>
    {
        var eventKey = GetEventKey<TEvent>();
        if (_eventHandlers.ContainsKey(eventKey))
        {
            return _eventHandlers[eventKey].Add(typeof(TEventHandler));
        }
        else
        {
            return _eventHandlers.TryAdd(eventKey, new HashSet<Type>()
            {
                typeof(TEventHandler)
            });
        }
    }

    public bool Clear()
    {
        _eventHandlers.Clear();
        return true;
    }

    public ICollection<Type> GetEventHandlerTypes<TEvent>() where TEvent : IEventBase
    {
        if(_eventHandlers.Count == 0)
            return  new Type[0];
        var eventKey = GetEventKey<TEvent>();
        if (_eventHandlers.TryGetValue(eventKey, out var handlers))
        {
            return handlers;
        }
        return new Type[0];
    }

    public string GetEventKey<TEvent>()
    {
        return typeof(TEvent).FullName;
    }

    public bool HasSubscriptionsForEvent<TEvent>() where TEvent : IEventBase
    {
        if(_eventHandlers.Count == 0)
            return false;

        var eventKey = GetEventKey<TEvent>();
        return _eventHandlers.ContainsKey(eventKey);
    }

    public bool RemoveSubscription<TEvent, TEventHandler>()
        where TEvent : IEventBase
        where TEventHandler : IEventHandler<TEvent>
    {
        if(_eventHandlers.Count == 0)
            return false;

        var eventKey = GetEventKey<TEvent>();
        if (_eventHandlers.ContainsKey(eventKey))
        {
            return _eventHandlers[eventKey].Remove(typeof(TEventHandler));
        }
        return false;
    }
}

EventBus 的实现,从上面可以看到 EventStore 保存的是 IEventHandler 对应的 Type,在 Publish 的时候根据 Type 从 IoC 容器中取得相应的 Handler 即可,如果没有在 IoC 容器中找到对应的类型,则会尝试创建一个类型实例,然后调用 IEventHandlerHandle 方法,代码如下:

/// <summary>
/// EventBus in process
/// </summary>
public class EventBus : IEventBus
{
    private static readonly ILogHelperLogger Logger = Helpers.LogHelper.GetLogger<EventBus>();

    private readonly IEventStore _eventStore;
    private readonly IServiceProvider _serviceProvider;

    public EventBus(IEventStore eventStore, IServiceProvider serviceProvider = null)
    {
        _eventStore = eventStore;
        _serviceProvider = serviceProvider ?? DependencyResolver.Current;
    }

    public bool Publish<TEvent>(TEvent @event) where TEvent : IEventBase
    {
        if (!_eventStore.HasSubscriptionsForEvent<TEvent>())
        {
            return false;
        }
        var handlers = _eventStore.GetEventHandlerTypes<TEvent>();
        if (handlers.Count > 0)
        {
            var handlerTasks = new List<Task>();
            foreach (var handlerType in handlers)
            {
                try
                {
                    if (_serviceProvider.GetServiceOrCreateInstance(handlerType) is IEventHandler<TEvent> handler)
                    {
                        handlerTasks.Add(handler.Handle(@event));
                    }
                }
                catch (Exception ex)
                {
                    Logger.Error(ex, $"handle event [{_eventStore.GetEventKey<TEvent>()}] error, eventHandlerType:{handlerType.FullName}");
                }
            }
            handlerTasks.WhenAll().ConfigureAwait(false);

            return true;
        }
        return false;
    }

    public bool Subscribe<TEvent, TEventHandler>()
        where TEvent : IEventBase
        where TEventHandler : IEventHandler<TEvent>
    {
        return _eventStore.AddSubscription<TEvent, TEventHandler>();
    }

    public bool Unsubscribe<TEvent, TEventHandler>()
        where TEvent : IEventBase
        where TEventHandler : IEventHandler<TEvent>
    {
        return _eventStore.RemoveSubscription<TEvent, TEventHandler>();
    }
}

项目实例

来看一个实际的项目中的使用,在我的活动室预约项目中有一个公告的模块,访问公告详情页面,这个公告的访问次数加1,把这个访问次数加1改成了用 EventBus 来实现,实际项目代码:https://github.com/WeihanLi/ActivityReservation/blob/67e2cb8e92876629a7af6dc051745dd8c7e9faeb/ActivityReservation/Startup.cs

  1. 定义 Event 以及 EventHandler
public class NoticeViewEvent : EventBase
{
    public Guid NoticeId { get; set; }

    // UserId
    // IP
    // ...
}

public class NoticeViewEventHandler : IEventHandler<NoticeViewEvent>
{
    public async Task Handle(NoticeViewEvent @event)
    {
        await DependencyResolver.Current.TryInvokeServiceAsync<ReservationDbContext>(async dbContext =>
        {
            var notice = await dbContext.Notices.FindAsync(@event.NoticeId);
            notice.NoticeVisitCount += 1;
            await dbContext.SaveChangesAsync();
        });
    }
}

这里的 Event 只定义了一个 NoticeId ,其实也可以把请求信息如IP/UA等信息加进去,在 EventHandler里处理以便日后数据分析。

  1. 注册 EventBus 相关服务以及 EventHandlers
services.AddSingleton<IEventBus, EventBus>();
services.AddSingleton<IEventStore, EventStoreInMemory>();
//register EventHandlers
services.AddSingleton<NoticeViewEventHandler>();
  1. 订阅事件
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IEventBus eventBus)
{
    eventBus.Subscribe<NoticeViewEvent, NoticeViewEventHandler>(); 
    // ...
}
  1. 发布事件
eventBus.Publish(new NoticeViewEvent { NoticeId = notice.NoticeId });

Reference

Guess you like

Origin www.cnblogs.com/weihanli/p/implement-a-simple-event-bus.html