隨著微服務的火熱,DDD(領域驅動設計模式)思想風起雲涌,衝擊著整個軟體生態系統。其中,事件匯流排那是必須知道的了,於是我便抱著一個學習DDD的心態搭建了一個博客網站,目前該網站正在建設階段,後續會不斷完善,這裡我只是講一下我裡面所用到的事件匯流排。 事件匯流排,我的理解就是發佈訂閱模式,這裡有一篇文章寫 ...
隨著微服務的火熱,DDD(領域驅動設計模式)思想風起雲涌,衝擊著整個軟體生態系統。其中,事件匯流排那是必須知道的了,於是我便抱著一個學習DDD的心態搭建了一個博客網站,目前該網站正在建設階段,後續會不斷完善,這裡我只是講一下我裡面所用到的事件匯流排。
事件匯流排,我的理解就是發佈訂閱模式,這裡有一篇文章寫的比較好,我就是按著這個文章來完成的事件匯流排:事件匯流排知多少。我之前按照他的文章結合自己寫的,但是今天又看了下自己寫的,發現好多都生疏了,所以覺得有必要來回憶下,這裡只是我個人的理解,如有不對請指出。
事件匯流排就肯定要有事件源,這裡我定義一個Command事件源:/// <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,個人站點處於建設階段,很多功能不完善,由於時間原因,所以進度比較慢,但是我也是每天回到家後都會去完善,自己做的飯再難吃也要吃完,自己做的網站,不好看也要用心呵護。