2017-04-20 10:53:17 +02:00
|
|
|
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
2017-03-16 13:30:01 +01:00
|
|
|
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
2017-04-20 10:53:17 +02:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2017-03-10 13:17:16 +01:00
|
|
|
|
using Newtonsoft.Json;
|
2017-04-20 10:53:17 +02:00
|
|
|
|
using Polly;
|
|
|
|
|
using Polly.Retry;
|
2017-03-09 15:56:34 +01:00
|
|
|
|
using RabbitMQ.Client;
|
|
|
|
|
using RabbitMQ.Client.Events;
|
2017-04-20 10:53:17 +02:00
|
|
|
|
using RabbitMQ.Client.Exceptions;
|
2017-03-09 15:56:34 +01:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2017-03-10 13:17:16 +01:00
|
|
|
|
using System.Linq;
|
2017-04-20 10:53:17 +02:00
|
|
|
|
using System.Net.Sockets;
|
2017-03-10 13:17:16 +01:00
|
|
|
|
using System.Reflection;
|
2017-03-09 15:56:34 +01:00
|
|
|
|
using System.Text;
|
2017-03-10 18:34:58 +01:00
|
|
|
|
using System.Threading.Tasks;
|
2017-03-09 15:56:34 +01:00
|
|
|
|
|
2017-03-16 13:30:01 +01:00
|
|
|
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
2017-03-09 15:56:34 +01:00
|
|
|
|
{
|
2017-03-17 15:57:57 +01:00
|
|
|
|
public class EventBusRabbitMQ : IEventBus, IDisposable
|
2017-03-09 15:56:34 +01:00
|
|
|
|
{
|
2017-04-20 10:53:17 +02:00
|
|
|
|
const string BROKER_NAME = "eshop_event_bus";
|
2017-03-10 13:17:16 +01:00
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
private readonly IRabbitMQPersisterConnection _persisterConnection;
|
|
|
|
|
private readonly ILogger<EventBusRabbitMQ> _logger;
|
2017-03-09 15:56:34 +01:00
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
private readonly Dictionary<string, List<IIntegrationEventHandler>> _handlers
|
|
|
|
|
= new Dictionary<string, List<IIntegrationEventHandler>>();
|
|
|
|
|
|
|
|
|
|
private readonly List<Type> _eventTypes
|
|
|
|
|
= new List<Type>();
|
|
|
|
|
|
|
|
|
|
private IModel _consumerChannel;
|
2017-04-20 16:44:07 +02:00
|
|
|
|
private string _queueName;
|
2017-04-20 10:53:17 +02:00
|
|
|
|
|
|
|
|
|
public EventBusRabbitMQ(IRabbitMQPersisterConnection persisterConnection, ILogger<EventBusRabbitMQ> logger)
|
2017-03-09 15:56:34 +01:00
|
|
|
|
{
|
2017-04-20 10:53:17 +02:00
|
|
|
|
_persisterConnection = persisterConnection ?? throw new ArgumentNullException(nameof(persisterConnection));
|
|
|
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
|
|
|
|
|
|
|
|
_consumerChannel = CreateConsumerChannel();
|
2017-03-09 15:56:34 +01:00
|
|
|
|
}
|
2017-03-17 15:57:57 +01:00
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
|
2017-03-15 18:42:47 -07:00
|
|
|
|
public void Publish(IntegrationEvent @event)
|
2017-03-09 15:56:34 +01:00
|
|
|
|
{
|
2017-04-20 10:53:17 +02:00
|
|
|
|
if (!_persisterConnection.IsConnected)
|
|
|
|
|
{
|
|
|
|
|
_persisterConnection.TryConnect();
|
|
|
|
|
}
|
2017-04-17 15:00:53 +02:00
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
var policy = RetryPolicy.Handle<BrokerUnreachableException>()
|
|
|
|
|
.Or<SocketException>()
|
|
|
|
|
.WaitAndRetry(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
|
|
|
|
|
{
|
|
|
|
|
_logger.LogWarning(ex.ToString());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
using (var channel = _persisterConnection.CreateModel())
|
2017-03-10 13:17:16 +01:00
|
|
|
|
{
|
2017-04-20 10:53:17 +02:00
|
|
|
|
var eventName = @event.GetType()
|
|
|
|
|
.Name;
|
|
|
|
|
|
|
|
|
|
channel.ExchangeDeclare(exchange: BROKER_NAME,
|
2017-03-10 13:17:16 +01:00
|
|
|
|
type: "direct");
|
2017-03-09 15:56:34 +01:00
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
var message = JsonConvert.SerializeObject(@event);
|
2017-03-09 15:56:34 +01:00
|
|
|
|
var body = Encoding.UTF8.GetBytes(message);
|
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
policy.Execute(() =>
|
|
|
|
|
{
|
|
|
|
|
channel.BasicPublish(exchange: BROKER_NAME,
|
2017-03-10 13:17:16 +01:00
|
|
|
|
routingKey: eventName,
|
2017-03-09 15:56:34 +01:00
|
|
|
|
basicProperties: null,
|
2017-04-20 10:53:17 +02:00
|
|
|
|
body: body);
|
|
|
|
|
});
|
2017-03-09 15:56:34 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-15 18:42:47 -07:00
|
|
|
|
public void Subscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
|
2017-03-09 15:56:34 +01:00
|
|
|
|
{
|
|
|
|
|
var eventName = typeof(T).Name;
|
2017-04-20 10:53:17 +02:00
|
|
|
|
|
|
|
|
|
if (_handlers.ContainsKey(eventName))
|
2017-03-09 15:56:34 +01:00
|
|
|
|
{
|
|
|
|
|
_handlers[eventName].Add(handler);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-04-20 10:53:17 +02:00
|
|
|
|
if (!_persisterConnection.IsConnected)
|
|
|
|
|
{
|
|
|
|
|
_persisterConnection.TryConnect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var channel = _persisterConnection.CreateModel())
|
|
|
|
|
{
|
2017-04-20 16:44:07 +02:00
|
|
|
|
channel.QueueBind(queue: _queueName,
|
2017-04-20 10:53:17 +02:00
|
|
|
|
exchange: BROKER_NAME,
|
|
|
|
|
routingKey: eventName);
|
|
|
|
|
|
|
|
|
|
_handlers.Add(eventName, new List<IIntegrationEventHandler>());
|
|
|
|
|
_handlers[eventName].Add(handler);
|
|
|
|
|
_eventTypes.Add(typeof(T));
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-09 15:56:34 +01:00
|
|
|
|
}
|
2017-04-20 10:53:17 +02:00
|
|
|
|
|
2017-03-09 15:56:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-15 18:42:47 -07:00
|
|
|
|
public void Unsubscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
|
2017-03-09 15:56:34 +01:00
|
|
|
|
{
|
|
|
|
|
var eventName = typeof(T).Name;
|
2017-04-20 10:53:17 +02:00
|
|
|
|
|
2017-03-09 15:56:34 +01:00
|
|
|
|
if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler))
|
|
|
|
|
{
|
|
|
|
|
_handlers[eventName].Remove(handler);
|
|
|
|
|
|
|
|
|
|
if (_handlers[eventName].Count == 0)
|
|
|
|
|
{
|
|
|
|
|
_handlers.Remove(eventName);
|
2017-03-10 13:17:16 +01:00
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
|
|
|
|
|
|
|
|
|
|
if (eventType != null)
|
2017-03-10 13:17:16 +01:00
|
|
|
|
{
|
2017-04-20 10:53:17 +02:00
|
|
|
|
_eventTypes.Remove(eventType);
|
|
|
|
|
|
|
|
|
|
if (!_persisterConnection.IsConnected)
|
|
|
|
|
{
|
|
|
|
|
_persisterConnection.TryConnect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var channel = _persisterConnection.CreateModel())
|
|
|
|
|
{
|
2017-04-20 16:44:07 +02:00
|
|
|
|
channel.QueueUnbind(queue: _queueName,
|
2017-04-20 10:53:17 +02:00
|
|
|
|
exchange: BROKER_NAME,
|
|
|
|
|
routingKey: eventName);
|
2017-04-20 16:44:07 +02:00
|
|
|
|
|
|
|
|
|
if (_handlers.Keys.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
_queueName = string.Empty;
|
|
|
|
|
|
|
|
|
|
_consumerChannel.Close();
|
|
|
|
|
}
|
2017-04-20 10:53:17 +02:00
|
|
|
|
}
|
2017-03-10 13:17:16 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-09 15:56:34 +01:00
|
|
|
|
|
2017-03-17 15:57:57 +01:00
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2017-04-20 16:44:07 +02:00
|
|
|
|
if (_consumerChannel != null)
|
|
|
|
|
{
|
|
|
|
|
_consumerChannel.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 14:23:25 +01:00
|
|
|
|
_handlers.Clear();
|
2017-03-17 15:57:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
private IModel CreateConsumerChannel()
|
2017-03-10 13:17:16 +01:00
|
|
|
|
{
|
2017-04-20 10:53:17 +02:00
|
|
|
|
if (!_persisterConnection.IsConnected)
|
2017-03-10 13:17:16 +01:00
|
|
|
|
{
|
2017-04-20 10:53:17 +02:00
|
|
|
|
_persisterConnection.TryConnect();
|
2017-03-22 14:23:25 +01:00
|
|
|
|
}
|
2017-03-09 15:56:34 +01:00
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
var channel = _persisterConnection.CreateModel();
|
2017-03-10 13:17:16 +01:00
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
channel.ExchangeDeclare(exchange: BROKER_NAME,
|
|
|
|
|
type: "direct");
|
2017-03-22 14:23:25 +01:00
|
|
|
|
|
2017-04-20 16:44:07 +02:00
|
|
|
|
_queueName = channel.QueueDeclare().QueueName;
|
|
|
|
|
|
2017-03-22 14:23:25 +01:00
|
|
|
|
var consumer = new EventingBasicConsumer(channel);
|
|
|
|
|
consumer.Received += async (model, ea) =>
|
|
|
|
|
{
|
|
|
|
|
var eventName = ea.RoutingKey;
|
|
|
|
|
var message = Encoding.UTF8.GetString(ea.Body);
|
|
|
|
|
|
|
|
|
|
await ProcessEvent(eventName, message);
|
|
|
|
|
};
|
2017-04-20 10:53:17 +02:00
|
|
|
|
|
2017-04-20 16:44:07 +02:00
|
|
|
|
channel.BasicConsume(queue: _queueName,
|
2017-03-22 14:23:25 +01:00
|
|
|
|
noAck: true,
|
|
|
|
|
consumer: consumer);
|
|
|
|
|
|
2017-04-20 10:53:17 +02:00
|
|
|
|
channel.CallbackException += (sender, ea) =>
|
|
|
|
|
{
|
|
|
|
|
_consumerChannel.Dispose();
|
|
|
|
|
_consumerChannel = CreateConsumerChannel();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return channel;
|
2017-03-09 15:56:34 +01:00
|
|
|
|
}
|
2017-03-14 14:15:34 +01:00
|
|
|
|
|
|
|
|
|
private async Task ProcessEvent(string eventName, string message)
|
|
|
|
|
{
|
|
|
|
|
if (_handlers.ContainsKey(eventName))
|
|
|
|
|
{
|
|
|
|
|
Type eventType = _eventTypes.Single(t => t.Name == eventName);
|
2017-04-20 10:53:17 +02:00
|
|
|
|
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
2017-03-14 14:15:34 +01:00
|
|
|
|
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
|
|
|
|
var handlers = _handlers[eventName];
|
|
|
|
|
|
|
|
|
|
foreach (var handler in handlers)
|
|
|
|
|
{
|
|
|
|
|
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-09 15:56:34 +01:00
|
|
|
|
}
|
|
|
|
|
}
|