事件總線,我的理解就是發布訂閱模式,這里有一篇文章寫的比較好,我就是按着這個文章來完成的事件總線:事件總線知多少。我之前按照他的文章結合自己寫的,但是今天又看了下自己寫的,發現好多都生疏了,所以覺得有必要來回憶下,這里只是我個人的理解,如有不對請指出。
/// <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,個人站點處於建設階段,很多功能不完善,由於時間原因,所以進度比較慢,但是我也是每天回到家后都會去完善,自己做的飯再難吃也要吃完,自己做的網站,不好看也要用心呵護。
