Event bus based on rabbitmq

Time:2020-9-19

In this era of hot micro service, if you don’t know a little bit of micro service related technology, there is no way to blow off the cattle. So it is necessary to understand and learn. So I recently looked at the knowledge related to microservices.
Microservices involve a wide range of knowledge. I’m just going to talk about event bus. Of course, there are excellent frameworks such as cap, but I just want to experience and understand the event bus and its workflow, so I wrote an event bus based on rabbitmq.
1. Run rabbitmq;
2. Create a solution and simulate the distribution as shown in the following figure (I learned about the gateway of microservices before, so there will be a gateway, so I need to run three programs and run consult to do service discovery)
 
3. Realize the publishing function of API1
     
1. Create ieventbus as an abstract interface. In fact, you can use multiple MQ to implement it. I just use rabbitmq here, so create an eventbus rabbitmq to implement the interface

 public interface IEventBus
    {
    }
   public class EventBusRabbitMQ : IEventBus
    {
    }

 

 
2, and then create a new class to implement the di injection of the event bus

serviceDescriptors.AddTransient();

3. Publish messages. In order to enable different services to have this message type and pass them as parameters, we need a base class as the message bus: eventdata. Then each message type defined by our service must inherit this class

 public class CreateUserEvent : EventData
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public DateTime CreateTime { get; set; }

    }

 

Since there are messages, there will be message events, and an EventHandler is needed to drive them. Therefore, each message object of a service should have an event driven class. Of course, this driver class should be in the subscriber, and the publisher should only be responsible for publishing messages. As for message processing, events should be implemented by subscribers, which will be discussed later. The release of messages is based on rabbitmq. There are many examples of implementation on the Internet. Here is just my writing method, with eventdata as the parameter:

 public void Publish(EventData eventData)
        {
            string routeKey = eventData.GetType().Name;
            channel.QueueDeclare(queueName, true, false, false, null);
            string message = JsonConvert.SerializeObject(eventData);
            byte[] body = Encoding.UTF8.GetBytes(message);
            channel.BasicPublish(exchangeName, routeKey, null, body);
        }

 

Then access Apil to simulate message publishing

 [HttpGet]
        [Route("api/user/eventbus")]
        public IActionResult Eventbus()
        {
            CreateUserEvent user = new CreateUserEvent();
            user.Name = "hanh";
            user.Address = "hubei";
            user.CreateTime = DateTime.Now;
            _eventBus.Publish(user);
            return Ok("ok");
        }

 

4. Realize the subscription function of api2
Just now that the subscription should implement event processing of messages, there will be a usereventhandler that inherits the EventHandler to process the message

public class UserEventHandler : IEventHandler, IEventHandler
    {
        private readonly ILogger _logger;
        public UserEventHandler(ILogger logger)
        {
            _logger = logger;
        }
        public async Task Handler(CreateUserEvent eventData)
        {
            _logger.LogInformation(JsonConvert.SerializeObject(eventData));
             await Task.FromResult(0);
        }

        public async Task Handler(UpdateUserEvent eventData)
        {
            await Task.FromResult(0);
        }

    }

 

Then it will start to process the subscription. The general idea is to use the eventdata as the key, and then each eventdata should have a generic EventHandler < > interface, and then store it as value in memory. At the same time, rabbitmq binds the message queue. When the message arrives, it automatically processes the message event, gets the type name of the published message, and then according to the type name, we can automatically process the message event Get the type of its eventdata, and then get its implementation class through the built-in IOC of. Net core according to this type. The type of each eventdata will match with different EventHandler, so crud will be completed. At this point, the general subscription has been implemented:

public void AddSub()
             where T : EventData
             where TH : IEventHandler
        {
            Type eventDataType = typeof(T);
            Type handlerType = typeof(TH);
            if (!eventhandlers.ContainsKey(typeof(T)))
                eventhandlers.TryAdd(eventDataType, handlerType);
            if(!eventTypes.ContainsKey(eventDataType.Name))
                eventTypes.TryAdd(eventDataType.Name, eventDataType);
            if (assemblyTypes != null)
            {
               Type implementationType = assemblyTypes.FirstOrDefault(s => handlerType.IsAssignableFrom(s));
                if (implementationType == null)
                    Throw new argumentnullexception ("implementation class of {0} not found)", handlerType.FullName );
                _serviceDescriptors.AddTransient(handlerType, implementationType);
            }
        }
   public void Subscribe()
            where T : EventData
            where TH : IEventHandler
        {
            _eventBusManager.AddSub();
            channel.QueueBind(queueName, exchangeName, typeof(T).Name);
            channel.QueueDeclare(queueName, true, false, false, null);
            var consumer = new EventingBasicConsumer(channel);
            consumer.Received +=async (model, ea) =>
            {
                string eventName = ea.RoutingKey;
                byte[] resp = ea.Body.ToArray();
                string body = Encoding.UTF8.GetString(resp);
                _log.LogInformation(body);
                try
                {
                    Type eventType = _eventBusManager.FindEventType(eventName);
                    T eventData = (T)JsonConvert.DeserializeObject(body, eventType);
                    IEventHandler eventHandler = _eventBusManager.FindHandlerType(eventType) as IEventHandler;
                    await eventHandler.Handler(eventData);
                }
                catch (Exception ex)
                {
                    throw ex;
                }

            };
            channel.BasicConsume(queueName, true, consumer);
        }

 

5. Test, access API1, publish successfully, and then api2 will print the information at the same time:

Finally, I will post the core code to you. If you want to see the complete code, please visit the addresshttps://github.com/Hansdas/Micro
 

using Micro.Core.EventBus.RabbitMQ;
using System;
using System.Collections.Generic;
using System.Text;

namespace Micro.Core.EventBus
{
    public interface IEventBus
    {
        /// 
        ///Release
        /// 
        /// 
        void Publish(EventData eventData);
        /// 
        ///Subscription
        /// 
        /// 
        /// 
        void Subscribe()
            where T : EventData
            where TH : IEventHandler;
        /// 
        ///Unsubscribe
        /// 
        /// 
        /// 
        void Unsubscribe()
             where T : EventData
             where TH : IEventHandler;
    }
}

 

    

using log4net;
using Micro.Core.EventBus.RabbitMQ.IImplementation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Micro.Core.EventBus.RabbitMQ
{
    public class EventBusRabbitMQ : IEventBus
    {
        /// 
        ///Queue name
        /// 
        private string queueName = "QUEUE";
        /// 
        ///Switch name
        /// 
        private string exchangeName = "directName";
        /// 
        ///Exchange type
        /// 
        private string exchangeType = "direct";
        private IFactoryRabbitMQ _factory;
        private IEventBusManager _eventBusManager;
        private ILogger _log;
        private readonly IConnection connection;
        private readonly IModel channel;
        public EventBusRabbitMQ(IFactoryRabbitMQ factory, IEventBusManager eventBusManager, ILogger log)
        {
            _factory = factory;
            _eventBusManager = eventBusManager;
            _eventBusManager.OnRemoveEventHandler += OnRemoveEvent;
            _log = log;
            connection = _factory.CreateConnection();
            channel = connection.CreateModel();
        }
        private void OnRemoveEvent(object sender, ValueTuple args)
        {
            channel.QueueUnbind(queueName, exchangeName, args.Item1.Name);
        }
        public void Publish(EventData eventData)
        {
            string routeKey = eventData.GetType().Name;
            channel.QueueDeclare(queueName, true, false, false, null);
            string message = JsonConvert.SerializeObject(eventData);
            byte[] body = Encoding.UTF8.GetBytes(message);
            channel.BasicPublish(exchangeName, routeKey, null, body);
        }

        public void Subscribe()
            where T : EventData
            where TH : IEventHandler
        {
            _eventBusManager.AddSub();
            channel.QueueBind(queueName, exchangeName, typeof(T).Name);
            channel.QueueDeclare(queueName, true, false, false, null);
            var consumer = new EventingBasicConsumer(channel);
            consumer.Received +=async (model, ea) =>
            {
                string eventName = ea.RoutingKey;
                byte[] resp = ea.Body.ToArray();
                string body = Encoding.UTF8.GetString(resp);
                _log.LogInformation(body);
                try
                {
                    Type eventType = _eventBusManager.FindEventType(eventName);
                    T eventData = (T)JsonConvert.DeserializeObject(body, eventType);
                    IEventHandler eventHandler = _eventBusManager.FindHandlerType(eventType) as IEventHandler;
                    await eventHandler.Handler(eventData);
                }
                catch (Exception ex)
                {
                    throw ex;
                }

            };
            channel.BasicConsume(queueName, true, consumer);
        }

        public void Unsubscribe()
           where T : EventData
           where TH : IEventHandler
        {
            if (_eventBusManager.HaveAddHandler(typeof(T)))
            {
                _eventBusManager.RemoveEventSub();
            }
        }
    }
}

 

using System;
using System.Collections.Generic;
using System.Text;

namespace Micro.Core.EventBus
{
    public interface IEventBusManager
    {
        /// 
        ///Unsubscribe events
        /// 
        event EventHandler> OnRemoveEventHandler;
        /// 
        ///Subscription
        /// 
        /// 
        /// 
        void AddSub()
            where T : EventData
            where TH : IEventHandler;
        /// 
        ///Unsubscribe
        /// 
        /// 
        /// 
        void RemoveEventSub()
            where T : EventData
            where TH : IEventHandler;
        /// 
        ///Include entity type
        /// 
        /// 
        /// 
        bool HaveAddHandler(Type eventDataType);
        /// 
        ///Find type by entity name
        /// 
        /// 
        /// 
        Type FindEventType(string eventName);
        /// 
        ///Find its domain event driver based on the entity type
        /// 
        /// 
        /// 
        object FindHandlerType(Type eventDataType);
    }
}

 

using Micro.Core.Configure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Micro.Core.EventBus
{
    internal class EventBusManager : IEventBusManager
    {
        public event EventHandler> OnRemoveEventHandler;
        private static ConcurrentDictionary eventhandlers=new ConcurrentDictionary();
        private readonly ConcurrentDictionary eventTypes = new ConcurrentDictionary();
        private readonly IList assemblyTypes;
        private readonly IServiceCollection _serviceDescriptors;
        private Func _buildServiceProvider;
        public EventBusManager(IServiceCollection serviceDescriptors,Func buildServiceProvicer)
        {
            _serviceDescriptors = serviceDescriptors;
            _buildServiceProvider = buildServiceProvicer;
            string dllName = ConfigurationProvider.configuration.GetSection("EventHandler.DLL").Value;
            if (!string.IsNullOrEmpty(dllName))
            {
                assemblyTypes = Assembly.Load(dllName).GetTypes();
            }
        }
        private void OnRemoveEvent(Type eventDataType, Type handler)
        {
            if (OnRemoveEventHandler != null)
            {
                OnRemoveEventHandler(this, new ValueTuple(eventDataType, handler));
            }
        }
        public void AddSub()
             where T : EventData
             where TH : IEventHandler
        {
            Type eventDataType = typeof(T);
            Type handlerType = typeof(TH);
            if (!eventhandlers.ContainsKey(typeof(T)))
                eventhandlers.TryAdd(eventDataType, handlerType);
            if(!eventTypes.ContainsKey(eventDataType.Name))
                eventTypes.TryAdd(eventDataType.Name, eventDataType);
            if (assemblyTypes != null)
            {
               Type implementationType = assemblyTypes.FirstOrDefault(s => handlerType.IsAssignableFrom(s));
                if (implementationType == null)
                    Throw new argumentnullexception ("implementation class of {0} not found)", handlerType.FullName );
                _serviceDescriptors.AddTransient(handlerType, implementationType);
            }
        }
        public void RemoveEventSub()
            where T : EventData
            where TH : IEventHandler
        {

            OnRemoveEvent(typeof(T), typeof(TH));
        }
        public bool HaveAddHandler(Type eventDataType)
        {
            if (eventhandlers.ContainsKey(eventDataType))
                return true;
            return false;
        }
        public Type FindEventType(string eventName)
        {
            if(!eventTypes.ContainsKey(eventName))
                throw new ArgumentException( string.Format ("eventtypes does not have key for class name {0}", eventName));
            return eventTypes[eventName];
        }
        public object FindHandlerType(Type eventDataType)
        {
            if(!eventhandlers.ContainsKey(eventDataType))
                throw new ArgumentException( string.Format ("there is no key of type {0}" for "eventhandlers", eventDataType.FullName );
           var obj = _buildServiceProvider(_serviceDescriptors).GetService(eventhandlers[eventDataType]);
            if (eventhandlers[eventDataType].IsAssignableFrom(obj.GetType()))
                return obj;
            return null;
        }
    }

}

 

using Micro.Core.Configure;                               
using Micro.Core.EventBus.RabbitMQ;
using Micro.Core.EventBus.RabbitMQ.IImplementation;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Micro.Core.EventBus
{
   public static class EventBusBuilder
    {
        public static EventBusOption eventBusOption;
        public static IServiceCollection AddEventBus(this IServiceCollection serviceDescriptors)
        {
            eventBusOption= ConfigurationProvider.GetModel("EventBusOption");
            switch (eventBusOption.MQProvider)
            {
                case MQProvider.RabbitMQ:
                    serviceDescriptors.AddTransient();
                    serviceDescriptors.AddTransient(typeof(IFactoryRabbitMQ), factiory => {
                        return new FactoryRabbitMQ(eventBusOption);
                    });
                    break;
            }
            EventBusManager eventBusManager = new EventBusManager(serviceDescriptors,s=>s.BuildServiceProvider());
            serviceDescriptors.AddSingleton(eventBusManager);
            return serviceDescriptors;
        }

    }
}

api1

 public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddEventBus();
        }
     
    }

 

api2:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Micro.Core.Configure;
using Micro.Core.Consul;
using Micro.Core.EventBus;
using Micro.Services.Domain;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ConfigurationProvider = Micro.Core.Configure.ConfigurationProvider;

namespace WebApi3
{
    public class Startup
    {

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddEventBus();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            var eventBus= app.ApplicationServices.GetRequiredService();
            eventBus.Subscribe>();
            eventBus.Subscribe>();
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

        }
    }
}

 

PS: I haven’t tried unsubscribing yet. I’ve read many people’s methods of unsubscribing based on the idea of events. I can’t understand why, because I think it’s better to define a method to implement it directly.

 

Transferred fromEvery day blog, welcome to visit