zoukankan      html  css  js  c++  java
  • .net core下使用事件总线

     
          随着微服务的火热,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,个人站点处于建设阶段,很多功能不完善,由于时间原因,所以进度比较慢,但是我也是每天回到家后都会去完善,自己做的饭再难吃也要吃完自己做的网站,不好看也要用心呵护。 

          

         

          

  • 相关阅读:
    初认识AngularJS
    (imcomplete) UVa 10127 Ones
    UVa 10061 How many zero's and how many digits?
    UVa 11728 Alternate Task
    UVa 11490 Just Another Problem
    UVa 10673 Play with Floor and Ceil
    JSON对象和字符串的收发(JS客户端用typeof()进行判断非常重要)
    HTML.ActionLink 和 Url.Action 的区别
    EASYUI TREE得到当前节点数据的GETDATA方法
    jqueery easyui tree把已选中的节点数据拼成json或者数组(非常重要)
  • 原文地址:https://www.cnblogs.com/MrHanBlog/p/13031671.html
Copyright © 2011-2022 走看看