事件總線,我的理解就是發布訂閱模式,這里有一篇文章寫的比較好,我就是按着這個文章來完成的事件總線:事件總線知多少。我之前按照他的文章結合自己寫的,但是今天又看了下自己寫的,發現好多都生疏了,所以覺得有必要來回憶下,這里只是我個人的理解,如有不對請指出。
/// <summary> /// 領域命令基類(此處文章里我稱之為事件源) /// </summary> public class Command { }
然后我根據事件源來定義一個事件源處理的接口和它的實現類:
/// <summary> /// 創建用戶領域命令(創建事件源) /// </summary> public class CreateUserCommand: Command { public CreateUserCommand(User user) { User = user; } public User User { get; private set; } } /// <summary> /// 用戶命令處理程序(處理事件源) /// </summary> public class UserCommandHandler : ICommandHandler<CreateUserCommand>, { private readonly IUserRepository _userRepository; private readonly IEventBus _eventBus; public UserCommandHandler(IUserRepository userRepository, IEventBus eventBus) { _userRepository = userRepository; _eventBus = eventBus; } public void Handler(CreateUserCommand command) { int count = _userRepository.SelectCountByAccount(command.User.Account); if (count > 0) { _eventBus.RaiseEvent(new NotifyValidation("該賬號已存在")); return; } _userRepository.Insert(command.User); } }
此處我覺得已經完成了關於事件源的功能,那么我們就來思考根據這個事件源來處理發布訂閱的關系。
就那注冊用戶功能來說,前面已經將注冊用戶的事件源已經寫好了,那么發布訂閱怎么處理呢?首先,我們應該清楚一個邏輯,那就是頁面生成用戶信息,后端獲取信息生成UserModel,然后我們根據UserModel轉為我們需要的CreateUserCommand,然后我們ICommandHandler根據CreateUserCommand來調用Handler,這是一個簡單的調用邏輯。那么IcommandHnadler怎么知道調用哪一個Handler呢?那就是我將事件源和事件源處理類存入集合里面,這樣我以后就會根據Command來獲取到我的ICommandHandler了,又因為.net core遵循依賴注入原則,所以我需要往容器了注入ICommander和他的實現類,就是UserCommandhandler,這個時候可以說是已經將事件源都注冊到了內存里了,以下是我的相關代碼:
/// <summary> /// 臨時存儲類型數組 /// </summary> private static Type[] serviceTypes = Assembly.Load("Blog.Domain").GetTypes(); private static ConcurrentDictionary<Type, IList<Type>> handlerMapping = new ConcurrentDictionary<Type, IList<Type>>(); public static IList<Type> GetOrAddHandlerMapping(this Type eventType) { return handlerMapping.GetOrAdd(eventType,(Type type)=>new List<Type>()); } /// <summary> /// 注冊事件總線(事件源) /// </summary> /// <typeparam name="TImplementation">ICommandler<CreateUserCommand></typeparam> /// <typeparam name="TService">CreateUserCommand</typeparam> /// <param name="serviceDescriptors"></param> public static void AddEventBus<TImplementation, TService>(this IServiceCollection serviceDescriptors) { Type handler = typeof(TImplementation); Type serviceType = serviceTypes.FirstOrDefault(s => handler.IsAssignableFrom(s));//獲得接口的實現類 if (serviceType == null) throw new ArgumentNullException(string.Format("類型{0}未找到實現類", handler.FullName)); serviceDescriptors.AddTransient(handler, serviceType);//.net core自帶的IOC容器 GetOrAddHandlerMapping(typeof(TService)).Add(handler);//將事件源和事件源處理程序注冊到內存里,可以說生成了一個訂閱列表 }
接下來我們再看發布與訂閱,我要先定義一個發布訂閱的中間件,
/// <summary> /// 中間件 /// </summary> public interface IEventBus { /// <summary> /// 發布 /// </summary> /// <typeparam name="TEventData"></typeparam> /// <param name="eventData"></param> void Publish<TCommand>(TCommand command) where TCommand : Command; }
然后還有它的實現類,處理邏輯就是根據UserCommandHandler去ConcurrentDictionary里找到它的對應的ICommandHandler,然后在從IOC容器找到ICommandHandler的實現類,然后執行里面的方法,如下:
public sealed class EventBus : IEventBus { private IServiceProvider _serviceProvider; public EventBus(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } /// <summary> /// 發布事件 /// </summary> /// <typeparam name="TEventData"></typeparam> /// <param name="eventData"></param> public void Publish<TCommand>(TCommand command) where TCommand : Command { IList<Type> types=typeof(TCommand).GetOrAddHandlerMapping(); if (types == null || types.Count == 0) throw new ServiceException("事件總線未注冊:" + typeof(TCommand).Name); foreach (var type in types)//從訂閱列表里尋找 { object obj = _serviceProvider.GetService(type); if(type.IsAssignableFrom(obj.GetType())) { ICommandHandler<TCommand> handler = obj as ICommandHandler<TCommand>; if (handler != null) handler.Handler(command);// } } } }
這個時候可以說已經完成了發布訂閱,程序生成CreateUserCommand,這里我的理解為就是發布,調用Publish方法,這里我覺得就是訂閱,然后最后執行Handler完成業務邏輯:
public class UserService : IUserService { private readonly IUserRepository _userRepository; private readonly IEventBus _eventBus; /// <summary> /// 根據UserModel轉實體 /// </summary> /// <param name="userModel"></param> /// <returns></returns> private User TransferModel(UserModel userModel) { return user; } public UserService(IUserRepository userRepository, IEventBus eventBus) { _userRepository = userRepository; _eventBus = eventBus; } public void Insert(UserModel userModel) { userModel.Password = EncrypUtil.MD5Encry(userModel.Password); var command = new CreateUserCommand(TransferModel(userModel));//創建事件源 _eventBus.Publish(command);//發布命令 } }
還有就是最后的IServiceCollection注入了:
/// <summary> /// 服務集合 /// </summary> /// <param name="services"></param> public static void AddServices(this IServiceCollection services) { services.AddTransient<IUserRepository, UserRepository>(); services.AddTransient<IUserService, UserService>(); services.AddEventBus<ICommandHandler<CreateUserCommand>, CreateUserCommand>();
services.AddTransient<IEventBus,EventBus>() services.DisposeServiceTypes(); }
以上就是我對事件總線的具體應用,希望有大佬能指出我這菜鳥的不足指出!
好記性不如爛筆頭,所以我把這個玩意用到了我的網站里面,我的個人站點的地址是:www.ttblog.site,源代碼的地址是:https://github.com/Hansdas/BlogH.git,個人站點處於建設階段,很多功能不完善,由於時間原因,所以進度比較慢,但是我也是每天回到家后都會去完善,自己做的飯再難吃也要吃完,自己做的網站,不好看也要用心呵護。