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:
Room service using micro EventBus achieve decoupling between systems:
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.
Why use EventBus
- Decoupling (decoupling between systems easy to achieve)
- 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)
- System Audit (each event is irrevocable, each event is traceable)
- ...
EventBus overall architecture:
IEventBase
: All events must implement this interface, this interface defines an event unique idEventId
incidents and eventsEventAt
IEventHandler
: Defines aHandle
method to handle the corresponding event
IEventStore
: Processing to store all of the events, save the eventIEventHandler
, generally not directly operate, operated by EventBus subscriptions and unsubscribeEventStore
IEventBus
: To publish / subscribe / unsubscribe events, one event andIEventHandler
saved to EventStore or removed from the EventStore
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 是 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 容器中找到对应的类型,则会尝试创建一个类型实例,然后调用 IEventHandler
的 Handle
方法,代码如下:
/// <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
- 定义 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里处理以便日后数据分析。
- 注册 EventBus 相关服务以及 EventHandlers
services.AddSingleton<IEventBus, EventBus>();
services.AddSingleton<IEventStore, EventStoreInMemory>();
//register EventHandlers
services.AddSingleton<NoticeViewEventHandler>();
- 订阅事件
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IEventBus eventBus)
{
eventBus.Subscribe<NoticeViewEvent, NoticeViewEventHandler>();
// ...
}
- 发布事件
eventBus.Publish(new NoticeViewEvent { NoticeId = notice.NoticeId });
Reference
- https://github.com/dotnet-architecture/eShopOnContainers
- https://docs.microsoft.com/zh-cn/previous-versions/msp-n-p/jj591559(v=pandp.10)
- https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/integration-event-based-microservice-communications
- https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/subscribe-events
- https://github.com/sheng-jie/EventBus
- https://github.com/WeihanLi/ActivityReservation