Add Basket handler and subscription to events. Change in EventBus to use broker and one message queue per microservice http://www.rabbitmq.com/tutorials/tutorial-four-dotnet.html

This commit is contained in:
dsanz 2017-03-10 13:17:16 +01:00
parent 18a402044e
commit 5b38a49f11
10 changed files with 137 additions and 66 deletions

View File

@ -39,6 +39,10 @@
<PackageReference Include="Swashbuckle" Version="6.0.0-beta902" /> <PackageReference Include="Swashbuckle" Version="6.0.0-beta902" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Common\Infrastructure\Infrastructure.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="Dockerfile"> <None Update="Dockerfile">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>

View File

@ -0,0 +1,25 @@
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Microsoft.eShopOnContainers.Services.Common.Infrastructure;
using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Basket.API.Events
{
public class CatalogPriceChangedHandler : IIntegrationEventHandler<CatalogPriceChanged>
{
private readonly IBasketRepository _repository;
public CatalogPriceChangedHandler()
{
//_repository = repository;
}
public void Handle(CatalogPriceChanged @event)
{
}
}
}

View File

@ -13,6 +13,9 @@ using Microsoft.Extensions.Options;
using System.Net; using System.Net;
using Swashbuckle.Swagger.Model; using Swashbuckle.Swagger.Model;
using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server; using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
using Microsoft.eShopOnContainers.Services.Common.Infrastructure;
using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog;
using Basket.API.Events;
namespace Microsoft.eShopOnContainers.Services.Basket.API namespace Microsoft.eShopOnContainers.Services.Basket.API
{ {
@ -76,6 +79,9 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
}); });
services.AddTransient<IBasketRepository, RedisBasketRepository>(); services.AddTransient<IBasketRepository, RedisBasketRepository>();
var eventBus = new EventBus();
services.AddSingleton<IEventBus>(eventBus);
eventBus.Subscribe<CatalogPriceChanged>(new CatalogPriceChangedHandler());
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -107,7 +107,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
//hook to run integration tests until POST methods are created //hook to run integration tests until POST methods are created
if (catalogTypeId.HasValue && catalogTypeId == 1) if (catalogTypeId.HasValue && catalogTypeId == 1)
{ {
_eventBus.Publish(new CatalogPriceChanged()); _eventBus.Publish(new CatalogPriceChanged(2, 10.4M));
} }
return Ok(model); return Ok(model);

View File

@ -6,15 +6,16 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog
{ {
public class CatalogPriceChanged : IIntegrationEvent public class CatalogPriceChanged : IIntegrationEvent
{ {
private readonly string _eventName = "catalogpricechanged"; public string Message { get { return "CatalogPriceChanged here!!"; } }
public string Name { public int ItemId { get; private set; }
get
{ public decimal NewPrice { get; private set; }
return _eventName;
} public CatalogPriceChanged(int itemId, decimal newPrice)
{
ItemId = itemId;
NewPrice = newPrice;
} }
}
public string Message { get { return "CatalogPriceChanged!!"; } }
}
} }

View File

@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog
{
public class CatalogPriceChangedHandler : IIntegrationEventHandler<CatalogPriceChanged>
{
public void Handle(CatalogPriceChanged @event)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,40 +1,47 @@
 
using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog;
using Newtonsoft.Json;
using RabbitMQ.Client; using RabbitMQ.Client;
using RabbitMQ.Client.Events; using RabbitMQ.Client.Events;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure
{ {
public class EventBus : IEventBus public class EventBus : IEventBus
{ {
private readonly string _brokerName = "event_bus";
private readonly Dictionary<string, List<IIntegrationEventHandler>> _handlers; private readonly Dictionary<string, List<IIntegrationEventHandler>> _handlers;
private readonly Dictionary<string, Tuple<IModel, IConnection>> _listeners; private readonly List<Type> _eventTypes;
private Tuple<IModel, IConnection> _connection;
private string _queueName;
public EventBus() public EventBus()
{ {
_handlers = new Dictionary<string, List<IIntegrationEventHandler>>(); _handlers = new Dictionary<string, List<IIntegrationEventHandler>>();
_listeners = new Dictionary<string, Tuple<IModel, IConnection>>(); _eventTypes = new List<Type>();
} }
public void Publish(IIntegrationEvent @event) public void Publish(IIntegrationEvent @event)
{ {
var eventName = @event.GetType().Name;
var factory = new ConnectionFactory() { HostName = "172.20.0.1" }; var factory = new ConnectionFactory() { HostName = "172.20.0.1" };
using (var connection = factory.CreateConnection()) using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel()) using (var channel = connection.CreateModel())
{ {
channel.QueueDeclare(queue: @event.Name, channel.ExchangeDeclare(exchange: _brokerName,
durable: false, type: "direct");
exclusive: false,
autoDelete: false,
arguments: null);
string message = ((CatalogPriceChanged)@event).Message; string message = JsonConvert.SerializeObject(@event);
var body = Encoding.UTF8.GetBytes(message); var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "", channel.BasicPublish(exchange: _brokerName,
routingKey: @event.Name, routingKey: eventName,
basicProperties: null, basicProperties: null,
body: body); body: body);
} }
@ -50,30 +57,14 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure
} }
else else
{ {
var factory = new ConnectionFactory() { HostName = "172.18.0.1" }; var channel = GetChannel();
var connection = factory.CreateConnection(); channel.QueueBind(queue: _queueName,
var channel = connection.CreateModel(); exchange: _brokerName,
routingKey: eventName);
channel.QueueDeclare(queue: eventName,
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
};
channel.BasicConsume(queue: "hello",
noAck: true,
consumer: consumer);
;
_listeners.Add(eventName, new Tuple<IModel, IConnection>(channel, connection));
_handlers.Add(eventName, new List<IIntegrationEventHandler>()); _handlers.Add(eventName, new List<IIntegrationEventHandler>());
_handlers[eventName].Add(handler); _handlers[eventName].Add(handler);
_eventTypes.Add(typeof(T));
} }
} }
@ -88,14 +79,70 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure
if (_handlers[eventName].Count == 0) if (_handlers[eventName].Count == 0)
{ {
_handlers.Remove(eventName); _handlers.Remove(eventName);
var eventType = _eventTypes.Single(e => e.Name == eventName);
_eventTypes.Remove(eventType);
_connection.Item1.QueueUnbind(queue: _queueName,
exchange: _brokerName,
routingKey: eventName);
var connectionItems =_listeners[eventName]; if (_handlers.Keys.Count == 0)
_listeners.Remove(eventName); {
_queueName = string.Empty;
_connection.Item1.Close();
_connection.Item2.Close();
}
connectionItems.Item1.Close();
connectionItems.Item2.Close();
} }
} }
} }
private IModel GetChannel()
{
if (_connection != null)
{
return _connection.Item1;
}
else
{
var factory = new ConnectionFactory() { HostName = "172.20.0.1" };
var connection = factory.CreateConnection();
var channel = connection.CreateModel();
channel.ExchangeDeclare(exchange: _brokerName,
type: "direct");
if (string.IsNullOrEmpty(_queueName))
{
_queueName = channel.QueueDeclare().QueueName;
}
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var eventName = ea.RoutingKey;
if (_handlers.ContainsKey(eventName))
{
var message = Encoding.UTF8.GetString(ea.Body);
Type eventType = _eventTypes.Single(t => t.Name == eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var handlers = _handlers[eventName];
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
foreach (var handler in handlers)
{
concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
}
};
channel.BasicConsume(queue: _queueName,
noAck: true,
consumer: consumer);
_connection = new Tuple<IModel, IConnection>(channel, connection);
return _connection.Item1;
}
}
} }
} }

View File

@ -6,6 +6,5 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure
{ {
public interface IIntegrationEvent public interface IIntegrationEvent
{ {
string Name { get; }
} }
} }

View File

@ -10,5 +10,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure
void Handle(TIntegrationEvent @event); void Handle(TIntegrationEvent @event);
} }
public interface IIntegrationEventHandler { } public interface IIntegrationEventHandler
{
}
} }

View File

@ -5,6 +5,7 @@
<RootNamespace>Microsoft.eShopOnContainers.Services.Common.Infrastructure</RootNamespace> <RootNamespace>Microsoft.eShopOnContainers.Services.Common.Infrastructure</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="RabbitMQ.Client" Version="4.1.1" /> <PackageReference Include="RabbitMQ.Client" Version="4.1.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>