Rethinking of EventBus/EventQueue

Intro

I've written two articles before, EventBus/EventQueue of wheel making series. In retrospect, I think there's something wrong with my current idea. At that time, I may have a little misunderstanding about the evanstore. If you are interested, please refer to it https://www.cnblogs.com/weihanli/p/implement-a-simple-event-bus.html/https://www.cnblogs.com/weihanli/p/implement-event-queue.html

Recently, the Event related logic has been reconstructed, the Event store has been modified, IEventHandlerFactory has been introduced, and the Event related components have been redesigned

Reconstructed Event

  • Event: abstract definition of event
  • EventHandler: abstract definition of event handler
  • EventHandlerFactory: event handler factory, used to get the event handler (New) according to the event type
  • EventPublisher: event publisher, for event Publishing
  • EventSubscriber: event subscriber, used to manage event subscriptions
  • Event subscription manager: event subscription manager, which adds a method to get event subscriber type based on event type
  • EventBus: event bus, composed of eventpublisher and EventSubscriber, which is used to publish and subscribe events conveniently
  • EventQueue: event queue. You can consider the mode of EventQueue when some messages are processed in sequence
  • EventStore: event store, event persistence store (in previous versions, the actual role of EventStore is an eventsubscription manager, which has been modified in the latest version update)

The above EventSubscriber and eventsubscription manager are generally not used directly, but can be processed with EventBus

EventHandlerFactory

This time, EventHandlerFactory is introduced to abstract the logic of obtaining event handler. In the original design, the type of EventHandler is obtained when processing Event, and then the Handle method of EventHandler is called to process Event after obtaining or creating a new instance of event handler from dependency injection framework. There are some redundancy

After using EventHandlerFactory, you can get a collection of EventHandler instances directly. Whether to instantiate or get from dependency injection is determined by EventHandlerFactory, which is very friendly to dependency injection. For a simple memory based EventBus, you don't need to call subscription after service registration To explicitly Subscribe, because the logic of subscription has been implicitly implemented when the service is re registered, so there is no need for eventsubscription manager to manage the subscription. The subscription information is in the dependency injection framework, such as CounterEvent. To get its subscription information, I only need to get ieventhandler < CounterEvent > from the dependency injection framework Instead of the original "EventStoreInMemory", the current EventSubscriptionManagerInMemory

Definition of EventHandlerFactory based on dependency injection:

public sealed class DependencyInjectionEventHandlerFactory : IEventHandlerFactory
{
    private readonly IServiceProvider _serviceProvider;

    public DependencyInjectionEventHandlerFactory(IServiceProvider serviceProvider = null)
    {
        _serviceProvider = serviceProvider ?? DependencyResolver.Current;
    }

    public ICollection<IEventHandler> GetHandlers(Type eventType)
    {
        var eventHandlerType = typeof(IEventHandler<>).MakeGenericType(eventType);
        return _serviceProvider.GetServices(eventHandlerType).Cast<IEventHandler>().ToArray();
    }
}

If you do not use dependency injection, you can also implement it according to ieventsubscription manager subscription information:

public sealed class DefaultEventHandlerFactory : IEventHandlerFactory
{
    private readonly IEventSubscriptionManager _subscriptionManager;
    private readonly ConcurrentDictionary<Type, ICollection<IEventHandler>> _eventHandlers = new ConcurrentDictionary<Type, ICollection<IEventHandler>>();
    private readonly IServiceProvider _serviceProvider;

    public DefaultEventHandlerFactory(IEventSubscriptionManager subscriptionManager, IServiceProvider serviceProvider = null)
    {
        _subscriptionManager = subscriptionManager;
        _serviceProvider = serviceProvider ?? DependencyResolver.Current;
    }

    public ICollection<IEventHandler> GetHandlers(Type eventType)
    {
        var eventHandlers = _eventHandlers.GetOrAdd(eventType, type =>
        {
            var handlerTypes = _subscriptionManager.GetEventHandlerTypes(type);
            var handlers = handlerTypes
                .Select(t => (IEventHandler)_serviceProvider.GetServiceOrCreateInstance(t))
                .ToArray();
            return handlers;
        });
        return eventHandlers;
    }
}

EventQueue Demo

Take a look at an example of an EventQueue based on asp.net For core, a HostedService is defined to implement an EventConsumer to consume the event information in the EventQueue

The EventConsumer is defined as follows:

public class EventConsumer : BackgroundService
{
    private readonly IEventQueue _eventQueue;
    private readonly IEventHandlerFactory _eventHandlerFactory;

    public EventConsumer(IEventQueue eventQueue, IEventHandlerFactory eventHandlerFactory)
    {
        _eventQueue = eventQueue;
        _eventHandlerFactory = eventHandlerFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var queues = await _eventQueue.GetQueuesAsync();
            if (queues.Count > 0)
            {
                await queues.Select(async q =>
                        {
                            var @event = await _eventQueue.DequeueAsync(q);
                            if (null != @event)
                            {
                                var handlers = _eventHandlerFactory.GetHandlers(@event.GetType());
                                if (handlers.Count > 0)
                                {
                                    await handlers
                                            .Select(h => h.Handle(@event))
                                            .WhenAll()
                                        ;
                                }
                            }
                        })
                        .WhenAll()
                    ;
            }

            await Task.Delay(1000, stoppingToken);
        }
    }
}

Define PageViewEvent and PageViewEventHandler to record and process request access records

public class PageViewEvent : EventBase
{
}

public class PageViewEventHandler : EventHandlerBase<PageViewEvent>
{
    public static int Count;

    public override Task Handle(PageViewEvent @event)
    {
        Interlocked.Increment(ref Count);
        return Task.CompletedTask;
    }
}

Events are very simple, and event processing only adds Count defined in PageViewEventHandler.

Service registration:

// Register event core components
// Will register EventBus, EventHandlerFactory, EventQueue, etc
services.AddEvents()
    // Register EventHanlder 
    .AddEventHandler<PageViewEvent, PageViewEventHandler>()
    ;
// Register eventqueuepublisher. The default registered IEventPublisher is EventBus
services.AddSingleton<IEventPublisher, EventQueuePublisher>();
// Register EventConsumer
services.AddHostedService<EventConsumer>();

Event Publishing: a middleware is defined to publish PageViewEvent as follows:

// pageView middleware
app.Use((context, next) =>
        {
            var eventPublisher = context.RequestServices.GetRequiredService<IEventPublisher>();
            eventPublisher.Publish(new PageViewEvent());

            return next();
        });

Then define an interface to get the Count in the PageViewEventHandler defined above

[Route("api/[controller]")]
public class EventsController : ControllerBase
{
    [HttpGet("pageViewCount")]
    public IActionResult Count()
    {
        return Ok(new { Count = PageViewEventHandler.Count });
    }
}

After running, visit the interface several times to see if the Count returned by the above interface will increase. Normally, every time you visit the interface, it will increase 1. The problem of concurrent access is not big, because every event is processed in order, even if concurrent access is irrelevant. After the event is published, it is processed in order in the queue, which is the purpose of introducing event queue (it seems that the atomic increment above is useless...) If we don't see the increase, we'll try again later. The event processing will be late, but it will always be processed. After all, it's asynchronous processing. Some delays are normal, and we have a 1 s delay above

More

For more information about the above events, please refer to the code: https://github.com/WeihanLi/WeihanLi.Common/tree/dev/src/WeihanLi.Common/Event

The author's level is limited. If there are any wrong points, please point out. Thank you very much

Reference

Tags: github

Posted on Sun, 24 May 2020 06:09:25 -0400 by JCBarry