Back to a working state
This commit is contained in:
parent
f5ae7322bd
commit
cb06ffd1b8
24
Config/IBindingContext.cs
Normal file
24
Config/IBindingContext.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Tapeti.Config
|
||||||
|
{
|
||||||
|
public delegate object ValueFactory(IMessageContext context);
|
||||||
|
|
||||||
|
|
||||||
|
public interface IBindingContext
|
||||||
|
{
|
||||||
|
Type MessageClass { get; set; }
|
||||||
|
IReadOnlyList<IBindingParameter> Parameters { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface IBindingParameter
|
||||||
|
{
|
||||||
|
ParameterInfo Info { get; }
|
||||||
|
bool HasBinding { get; }
|
||||||
|
|
||||||
|
void SetBinding(ValueFactory valueFactory);
|
||||||
|
}
|
||||||
|
}
|
9
Config/IBindingMiddleware.cs
Normal file
9
Config/IBindingMiddleware.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Tapeti.Config
|
||||||
|
{
|
||||||
|
public interface IBindingMiddleware
|
||||||
|
{
|
||||||
|
void Handle(IBindingContext context, Action next);
|
||||||
|
}
|
||||||
|
}
|
35
Config/IConfig.cs
Normal file
35
Config/IConfig.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Tapeti.Config
|
||||||
|
{
|
||||||
|
public interface IConfig
|
||||||
|
{
|
||||||
|
string Exchange { get; }
|
||||||
|
IDependencyResolver DependencyResolver { get; }
|
||||||
|
IReadOnlyList<IMessageMiddleware> MessageMiddleware { get; }
|
||||||
|
IEnumerable<IQueue> Queues { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface IQueue
|
||||||
|
{
|
||||||
|
bool Dynamic { get; }
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
IEnumerable<IBinding> Bindings { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface IBinding
|
||||||
|
{
|
||||||
|
Type Controller { get; }
|
||||||
|
MethodInfo Method { get; }
|
||||||
|
Type MessageClass { get; }
|
||||||
|
|
||||||
|
bool Accept(object message);
|
||||||
|
Task<object> Invoke(IMessageContext context, object message);
|
||||||
|
}
|
||||||
|
}
|
11
Config/IMessageContext.cs
Normal file
11
Config/IMessageContext.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Tapeti.Config
|
||||||
|
{
|
||||||
|
public interface IMessageContext
|
||||||
|
{
|
||||||
|
object Controller { get; }
|
||||||
|
object Message { get; }
|
||||||
|
IDictionary<string, object> Items { get; }
|
||||||
|
}
|
||||||
|
}
|
9
Config/IMessageMiddleware.cs
Normal file
9
Config/IMessageMiddleware.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Tapeti.Config
|
||||||
|
{
|
||||||
|
public interface IMessageMiddleware
|
||||||
|
{
|
||||||
|
void Handle(IMessageContext context, Action next);
|
||||||
|
}
|
||||||
|
}
|
9
Config/IMiddlewareBundle.cs
Normal file
9
Config/IMiddlewareBundle.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Tapeti.Config
|
||||||
|
{
|
||||||
|
public interface IMiddlewareBundle
|
||||||
|
{
|
||||||
|
IEnumerable<object> GetContents(IDependencyResolver dependencyResolver);
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,26 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.Eventing.Reader;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
|
using Tapeti.Config;
|
||||||
|
using Tapeti.Helpers;
|
||||||
|
|
||||||
namespace Tapeti.Connection
|
namespace Tapeti.Connection
|
||||||
{
|
{
|
||||||
public class TapetiConsumer : DefaultBasicConsumer
|
public class TapetiConsumer : DefaultBasicConsumer
|
||||||
{
|
{
|
||||||
private readonly TapetiWorker worker;
|
private readonly TapetiWorker worker;
|
||||||
private readonly IMessageSerializer messageSerializer;
|
private readonly IDependencyResolver dependencyResolver;
|
||||||
private readonly IQueueRegistration queueRegistration;
|
private readonly IReadOnlyList<IMessageMiddleware> messageMiddleware;
|
||||||
|
private readonly List<IBinding> bindings;
|
||||||
|
|
||||||
|
|
||||||
public TapetiConsumer(TapetiWorker worker, IMessageSerializer messageSerializer, IQueueRegistration queueRegistration)
|
public TapetiConsumer(TapetiWorker worker, IDependencyResolver dependencyResolver, IEnumerable<IBinding> bindings, IReadOnlyList<IMessageMiddleware> messageMiddleware)
|
||||||
{
|
{
|
||||||
this.worker = worker;
|
this.worker = worker;
|
||||||
this.messageSerializer = messageSerializer;
|
this.dependencyResolver = dependencyResolver;
|
||||||
this.queueRegistration = queueRegistration;
|
this.messageMiddleware = messageMiddleware;
|
||||||
|
this.bindings = bindings.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -24,22 +29,46 @@ namespace Tapeti.Connection
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var message = messageSerializer.Deserialize(body, properties);
|
var message = dependencyResolver.Resolve<IMessageSerializer>().Deserialize(body, properties);
|
||||||
if (message == null)
|
if (message == null)
|
||||||
throw new ArgumentException("Empty message");
|
throw new ArgumentException("Empty message");
|
||||||
|
|
||||||
if (queueRegistration.Accept(message))
|
var handled = false;
|
||||||
queueRegistration.Visit(message);
|
foreach (var binding in bindings.Where(b => b.Accept(message)))
|
||||||
else
|
{
|
||||||
|
var context = new MessageContext
|
||||||
|
{
|
||||||
|
Controller = dependencyResolver.Resolve(binding.Controller),
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
|
||||||
|
MiddlewareHelper.Go(messageMiddleware, (handler, next) => handler.Handle(context, next));
|
||||||
|
|
||||||
|
var result = binding.Invoke(context, message).Result;
|
||||||
|
if (result != null)
|
||||||
|
worker.Publish(result);
|
||||||
|
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handled)
|
||||||
throw new ArgumentException($"Unsupported message type: {message.GetType().FullName}");
|
throw new ArgumentException($"Unsupported message type: {message.GetType().FullName}");
|
||||||
|
|
||||||
worker.Respond(deliveryTag, ConsumeResponse.Ack);
|
worker.Respond(deliveryTag, ConsumeResponse.Ack);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
//TODO pluggable exception handling
|
worker.Respond(deliveryTag, ConsumeResponse.Requeue);
|
||||||
worker.Respond(deliveryTag, ConsumeResponse.Nack);
|
throw;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected class MessageContext : IMessageContext
|
||||||
|
{
|
||||||
|
public object Controller { get; set; }
|
||||||
|
public object Message { get; set; }
|
||||||
|
public IDictionary<string, object> Items { get; } = new Dictionary<string, object>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Tapeti.Config;
|
||||||
|
|
||||||
namespace Tapeti.Connection
|
namespace Tapeti.Connection
|
||||||
{
|
{
|
||||||
@ -16,9 +17,9 @@ namespace Tapeti.Connection
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task BindQueues(IEnumerable<IQueueRegistration> registrations)
|
public async Task BindQueues(IEnumerable<IQueue> queues)
|
||||||
{
|
{
|
||||||
await Task.WhenAll(registrations.Select(registration => workerFactory().Subscribe(registration)).ToList());
|
await Task.WhenAll(queues.Select(queue => workerFactory().Subscribe(queue)).ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
using RabbitMQ.Client.Exceptions;
|
using RabbitMQ.Client.Exceptions;
|
||||||
using RabbitMQ.Client.Framing;
|
using RabbitMQ.Client.Framing;
|
||||||
|
using Tapeti.Config;
|
||||||
using Tapeti.Tasks;
|
using Tapeti.Tasks;
|
||||||
|
|
||||||
namespace Tapeti.Connection
|
namespace Tapeti.Connection
|
||||||
@ -10,20 +12,23 @@ namespace Tapeti.Connection
|
|||||||
public class TapetiWorker
|
public class TapetiWorker
|
||||||
{
|
{
|
||||||
public TapetiConnectionParams ConnectionParams { get; set; }
|
public TapetiConnectionParams ConnectionParams { get; set; }
|
||||||
public string PublishExchange { get; set; }
|
public string Exchange { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
private readonly IDependencyResolver dependencyResolver;
|
||||||
|
private readonly IReadOnlyList<IMessageMiddleware> messageMiddleware;
|
||||||
private readonly IMessageSerializer messageSerializer;
|
private readonly IMessageSerializer messageSerializer;
|
||||||
private readonly IRoutingKeyStrategy routingKeyStrategy;
|
private readonly IRoutingKeyStrategy routingKeyStrategy;
|
||||||
private readonly Lazy<SingleThreadTaskQueue> taskQueue = new Lazy<SingleThreadTaskQueue>();
|
private readonly Lazy<SingleThreadTaskQueue> taskQueue = new Lazy<SingleThreadTaskQueue>();
|
||||||
private RabbitMQ.Client.IConnection connection;
|
private RabbitMQ.Client.IConnection connection;
|
||||||
private IModel channel;
|
private IModel channelInstance;
|
||||||
|
|
||||||
|
|
||||||
public TapetiWorker(IMessageSerializer messageSerializer, IRoutingKeyStrategy routingKeyStrategy)
|
public TapetiWorker(IDependencyResolver dependencyResolver, IReadOnlyList<IMessageMiddleware> messageMiddleware)
|
||||||
{
|
{
|
||||||
this.messageSerializer = messageSerializer;
|
this.dependencyResolver = dependencyResolver;
|
||||||
this.routingKeyStrategy = routingKeyStrategy;
|
this.messageMiddleware = messageMiddleware;
|
||||||
|
messageSerializer = dependencyResolver.Resolve<IMessageSerializer>();
|
||||||
|
routingKeyStrategy = dependencyResolver.Resolve<IRoutingKeyStrategy>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -35,29 +40,45 @@ namespace Tapeti.Connection
|
|||||||
var body = messageSerializer.Serialize(message, properties);
|
var body = messageSerializer.Serialize(message, properties);
|
||||||
|
|
||||||
(await GetChannel())
|
(await GetChannel())
|
||||||
.BasicPublish(PublishExchange, routingKeyStrategy.GetRoutingKey(message.GetType()), false,
|
.BasicPublish(Exchange, routingKeyStrategy.GetRoutingKey(message.GetType()), false,
|
||||||
properties, body);
|
properties, body);
|
||||||
}).Unwrap();
|
}).Unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Task Subscribe(string queueName, IQueueRegistration queueRegistration)
|
public Task Consume(string queueName, IEnumerable<IBinding> bindings)
|
||||||
{
|
{
|
||||||
return taskQueue.Value.Add(async () =>
|
return taskQueue.Value.Add(async () =>
|
||||||
{
|
{
|
||||||
(await GetChannel())
|
(await GetChannel()).BasicConsume(queueName, false, new TapetiConsumer(this, dependencyResolver, bindings, messageMiddleware));
|
||||||
.BasicConsume(queueName, false, new TapetiConsumer(this, messageSerializer, queueRegistration));
|
|
||||||
}).Unwrap();
|
}).Unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task Subscribe(IQueueRegistration registration)
|
public async Task Subscribe(IQueue queue)
|
||||||
{
|
{
|
||||||
var queueName = await taskQueue.Value.Add(async () =>
|
var queueName = await taskQueue.Value.Add(async () =>
|
||||||
registration.BindQueue(await GetChannel()))
|
{
|
||||||
.Unwrap();
|
var channel = await GetChannel();
|
||||||
|
|
||||||
await Subscribe(queueName, registration);
|
if (queue.Dynamic)
|
||||||
|
{
|
||||||
|
var dynamicQueue = channel.QueueDeclare();
|
||||||
|
|
||||||
|
foreach (var binding in queue.Bindings)
|
||||||
|
{
|
||||||
|
var routingKey = routingKeyStrategy.GetRoutingKey(binding.MessageClass);
|
||||||
|
channel.QueueBind(dynamicQueue.QueueName, Exchange, routingKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynamicQueue.QueueName;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.QueueDeclarePassive(queue.Name);
|
||||||
|
return queue.Name;
|
||||||
|
}).Unwrap();
|
||||||
|
|
||||||
|
await Consume(queueName, queue.Bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -91,10 +112,10 @@ namespace Tapeti.Connection
|
|||||||
|
|
||||||
return taskQueue.Value.Add(() =>
|
return taskQueue.Value.Add(() =>
|
||||||
{
|
{
|
||||||
if (channel != null)
|
if (channelInstance != null)
|
||||||
{
|
{
|
||||||
channel.Dispose();
|
channelInstance.Dispose();
|
||||||
channel = null;
|
channelInstance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReSharper disable once InvertIf
|
// ReSharper disable once InvertIf
|
||||||
@ -115,8 +136,8 @@ namespace Tapeti.Connection
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private async Task<IModel> GetChannel()
|
private async Task<IModel> GetChannel()
|
||||||
{
|
{
|
||||||
if (channel != null)
|
if (channelInstance != null)
|
||||||
return channel;
|
return channelInstance;
|
||||||
|
|
||||||
var connectionFactory = new ConnectionFactory
|
var connectionFactory = new ConnectionFactory
|
||||||
{
|
{
|
||||||
@ -134,7 +155,7 @@ namespace Tapeti.Connection
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
connection = connectionFactory.CreateConnection();
|
connection = connectionFactory.CreateConnection();
|
||||||
channel = connection.CreateModel();
|
channelInstance = connection.CreateModel();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -144,7 +165,7 @@ namespace Tapeti.Connection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return channel;
|
return channelInstance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
Default/BindingBufferStop.cs
Normal file
13
Default/BindingBufferStop.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using Tapeti.Config;
|
||||||
|
|
||||||
|
namespace Tapeti.Default
|
||||||
|
{
|
||||||
|
// End of the line...
|
||||||
|
public class BindingBufferStop : IBindingMiddleware
|
||||||
|
{
|
||||||
|
public void Handle(IBindingContext context, Action next)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,62 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Tapeti.Default
|
|
||||||
{
|
|
||||||
public class DefaultControllerFactory : IControllerFactory
|
|
||||||
{
|
|
||||||
private readonly Dictionary<Type, Func<object>> controllerConstructors = new Dictionary<Type, Func<object>>();
|
|
||||||
private readonly Func<IPublisher> publisherFactory;
|
|
||||||
|
|
||||||
public DefaultControllerFactory(Func<IPublisher> publisherFactory)
|
|
||||||
{
|
|
||||||
this.publisherFactory = publisherFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public object CreateController(Type controllerType)
|
|
||||||
{
|
|
||||||
Func<object> constructor;
|
|
||||||
if (!controllerConstructors.TryGetValue(controllerType, out constructor))
|
|
||||||
throw new ArgumentException($"Can not create unregistered controller {controllerType.FullName}");
|
|
||||||
|
|
||||||
return constructor();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void RegisterController(Type type)
|
|
||||||
{
|
|
||||||
controllerConstructors.Add(type, GetConstructor(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected Func<object> GetConstructor(Type type)
|
|
||||||
{
|
|
||||||
var constructors = type.GetConstructors();
|
|
||||||
|
|
||||||
ConstructorInfo publisherConstructor = null;
|
|
||||||
ConstructorInfo emptyConstructor = null;
|
|
||||||
|
|
||||||
foreach (var constructor in constructors)
|
|
||||||
{
|
|
||||||
var parameters = constructor.GetParameters();
|
|
||||||
if (parameters.Length > 0)
|
|
||||||
{
|
|
||||||
if (parameters.Length == 1 && parameters[0].ParameterType == typeof(IPublisher))
|
|
||||||
publisherConstructor = constructor;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
emptyConstructor = constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (publisherConstructor != null)
|
|
||||||
return () => publisherConstructor.Invoke(new object[] { publisherFactory() });
|
|
||||||
|
|
||||||
if (emptyConstructor != null)
|
|
||||||
return () => emptyConstructor.Invoke(null);
|
|
||||||
|
|
||||||
throw new ArgumentException($"Unable to construct type {type.Name}, a parameterless constructor or one with only an IPublisher parameter is required");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Tapeti.Default
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* !! IoC Container 9000 !!
|
|
||||||
*
|
|
||||||
* ...you probably want to replace this one as soon as possible.
|
|
||||||
*
|
|
||||||
* A Simple Injector implementation is provided in the Tapeti.SimpleInjector package.
|
|
||||||
*/
|
|
||||||
public class DefaultDependencyResolver : IDependencyInjector
|
|
||||||
{
|
|
||||||
private readonly Lazy<DefaultControllerFactory> controllerFactory;
|
|
||||||
private readonly Lazy<DefaultRoutingKeyStrategy> routingKeyStrategy = new Lazy<DefaultRoutingKeyStrategy>();
|
|
||||||
private readonly Lazy<DefaultMessageSerializer> messageSerializer = new Lazy<DefaultMessageSerializer>();
|
|
||||||
private readonly Lazy<ILogger> logger;
|
|
||||||
private IPublisher publisher;
|
|
||||||
|
|
||||||
|
|
||||||
public DefaultDependencyResolver()
|
|
||||||
{
|
|
||||||
controllerFactory = new Lazy<DefaultControllerFactory>(() => new DefaultControllerFactory(() => publisher));
|
|
||||||
|
|
||||||
logger = new Lazy<ILogger>(() =>
|
|
||||||
{
|
|
||||||
// http://stackoverflow.com/questions/6408588/how-to-tell-if-there-is-a-console
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// ReSharper disable once UnusedVariable
|
|
||||||
var dummy = Console.WindowHeight;
|
|
||||||
|
|
||||||
return new ConsoleLogger();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return new DevNullLogger();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public T Resolve<T>() where T : class
|
|
||||||
{
|
|
||||||
if (typeof(T) == typeof(IControllerFactory))
|
|
||||||
return (T)(controllerFactory.Value as IControllerFactory);
|
|
||||||
|
|
||||||
if (typeof(T) == typeof(IRoutingKeyStrategy))
|
|
||||||
return (T)(routingKeyStrategy.Value as IRoutingKeyStrategy);
|
|
||||||
|
|
||||||
if (typeof(T) == typeof(IMessageSerializer))
|
|
||||||
return (T)(messageSerializer.Value as IMessageSerializer);
|
|
||||||
|
|
||||||
if (typeof(T) == typeof(ILogger))
|
|
||||||
return (T)logger.Value;
|
|
||||||
|
|
||||||
return default(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void RegisterPublisher(IPublisher value)
|
|
||||||
{
|
|
||||||
publisher = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void RegisterController(Type type)
|
|
||||||
{
|
|
||||||
controllerFactory.Value.RegisterController(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
26
Default/DependencyResolverBinding.cs
Normal file
26
Default/DependencyResolverBinding.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Tapeti.Config;
|
||||||
|
|
||||||
|
namespace Tapeti.Default
|
||||||
|
{
|
||||||
|
public class DependencyResolverBinding : IBindingMiddleware
|
||||||
|
{
|
||||||
|
private readonly IDependencyResolver resolver;
|
||||||
|
|
||||||
|
|
||||||
|
public DependencyResolverBinding(IDependencyResolver resolver)
|
||||||
|
{
|
||||||
|
this.resolver = resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Handle(IBindingContext context, Action next)
|
||||||
|
{
|
||||||
|
next();
|
||||||
|
|
||||||
|
foreach (var parameter in context.Parameters.Where(p => !p.HasBinding && p.Info.ParameterType.IsClass))
|
||||||
|
parameter.SetBinding(messageContext => resolver.Resolve(parameter.Info.ParameterType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
Default/MessageBinding.cs
Normal file
23
Default/MessageBinding.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using Tapeti.Config;
|
||||||
|
|
||||||
|
namespace Tapeti.Default
|
||||||
|
{
|
||||||
|
public class MessageBinding : IBindingMiddleware
|
||||||
|
{
|
||||||
|
public void Handle(IBindingContext context, Action next)
|
||||||
|
{
|
||||||
|
if (context.Parameters.Count == 0)
|
||||||
|
throw new TopologyConfigurationException("First parameter must be a message class");
|
||||||
|
|
||||||
|
var parameter = context.Parameters[0];
|
||||||
|
if (!parameter.Info.ParameterType.IsClass)
|
||||||
|
throw new TopologyConfigurationException($"First parameter {parameter.Info.Name} must be a message class");
|
||||||
|
|
||||||
|
parameter.SetBinding(messageContext => messageContext.Message);
|
||||||
|
context.MessageClass = parameter.Info.ParameterType;
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Helpers/ConsoleHelper.cs
Normal file
22
Helpers/ConsoleHelper.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Tapeti.Helpers
|
||||||
|
{
|
||||||
|
public static class ConsoleHelper
|
||||||
|
{
|
||||||
|
// Source: http://stackoverflow.com/questions/6408588/how-to-tell-if-there-is-a-console
|
||||||
|
public static bool IsAvailable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// ReSharper disable once UnusedVariable - that's why it's called dummy
|
||||||
|
var dummy = Console.WindowHeight;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
Helpers/MiddlewareHelper.cs
Normal file
26
Helpers/MiddlewareHelper.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Tapeti.Helpers
|
||||||
|
{
|
||||||
|
public static class MiddlewareHelper
|
||||||
|
{
|
||||||
|
public static void Go<T>(IReadOnlyList<T> middleware, Action<T, Action> handle)
|
||||||
|
{
|
||||||
|
var handlerIndex = middleware.Count - 1;
|
||||||
|
if (handlerIndex == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Action handleNext = null;
|
||||||
|
|
||||||
|
handleNext = () =>
|
||||||
|
{
|
||||||
|
handlerIndex--;
|
||||||
|
if (handlerIndex >= 0)
|
||||||
|
handle(middleware[handlerIndex], handleNext);
|
||||||
|
};
|
||||||
|
|
||||||
|
handle(middleware[handlerIndex], handleNext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Tapeti
|
|
||||||
{
|
|
||||||
public interface IControllerFactory
|
|
||||||
{
|
|
||||||
object CreateController(Type controllerType);
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,12 +5,14 @@ namespace Tapeti
|
|||||||
public interface IDependencyResolver
|
public interface IDependencyResolver
|
||||||
{
|
{
|
||||||
T Resolve<T>() where T : class;
|
T Resolve<T>() where T : class;
|
||||||
|
object Resolve(Type type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public interface IDependencyInjector : IDependencyResolver
|
public interface IDependencyInjector : IDependencyResolver
|
||||||
{
|
{
|
||||||
void RegisterPublisher(IPublisher publisher);
|
void RegisterDefault<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
||||||
|
void RegisterPublisher(Func<IPublisher> publisher);
|
||||||
void RegisterController(Type type);
|
void RegisterController(Type type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using RabbitMQ.Client;
|
|
||||||
|
|
||||||
namespace Tapeti
|
|
||||||
{
|
|
||||||
public interface IQueueRegistration
|
|
||||||
{
|
|
||||||
string BindQueue(IModel channel);
|
|
||||||
|
|
||||||
bool Accept(object message);
|
|
||||||
Task Visit(object message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,4 @@
|
|||||||
using System;
|
namespace Tapeti
|
||||||
|
|
||||||
namespace Tapeti
|
|
||||||
{
|
{
|
||||||
public interface ISubscriber
|
public interface ISubscriber
|
||||||
{
|
{
|
||||||
|
20
ITopology.cs
20
ITopology.cs
@ -1,20 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Tapeti
|
|
||||||
{
|
|
||||||
public interface ITopology
|
|
||||||
{
|
|
||||||
IEnumerable<IQueue> Queues();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public interface IQueue
|
|
||||||
{
|
|
||||||
IEnumerable<IBinding> Bindings();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public interface IBinding
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
// General Information about an assembly is controlled through the following
|
||||||
|
@ -1,142 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using RabbitMQ.Client;
|
|
||||||
using Tapeti.Annotations;
|
|
||||||
|
|
||||||
namespace Tapeti.Registration
|
|
||||||
{
|
|
||||||
using MessageHandlerAction = Func<object, Task>;
|
|
||||||
|
|
||||||
public struct MessageHandler
|
|
||||||
{
|
|
||||||
public MessageHandlerAction Action;
|
|
||||||
public string Exchange;
|
|
||||||
public string RoutingKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class AbstractControllerRegistration : IQueueRegistration
|
|
||||||
{
|
|
||||||
private readonly Func<IControllerFactory> controllerFactoryFactory;
|
|
||||||
private readonly Type controllerType;
|
|
||||||
private readonly string defaultExchange;
|
|
||||||
private readonly Dictionary<Type, List<MessageHandler>> messageHandlers = new Dictionary<Type, List<MessageHandler>>();
|
|
||||||
|
|
||||||
|
|
||||||
protected AbstractControllerRegistration(Func<IControllerFactory> controllerFactoryFactory, Type controllerType, string defaultExchange)
|
|
||||||
{
|
|
||||||
this.controllerFactoryFactory = controllerFactoryFactory;
|
|
||||||
this.controllerType = controllerType;
|
|
||||||
this.defaultExchange = defaultExchange;
|
|
||||||
|
|
||||||
// ReSharper disable once VirtualMemberCallInConstructor - I know. What do you think this is, C++?
|
|
||||||
GetMessageHandlers(controllerType, (type, handler) =>
|
|
||||||
{
|
|
||||||
if (!messageHandlers.ContainsKey(type))
|
|
||||||
messageHandlers.Add(type, new List<MessageHandler> { handler });
|
|
||||||
else
|
|
||||||
messageHandlers[type].Add(handler);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected virtual void GetMessageHandlers(Type type, Action<Type, MessageHandler> add)
|
|
||||||
{
|
|
||||||
foreach (var method in type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
|
|
||||||
.Where(m => m.MemberType == MemberTypes.Method && m.DeclaringType != typeof(object))
|
|
||||||
.Select(m => (MethodInfo)m))
|
|
||||||
{
|
|
||||||
Type messageType;
|
|
||||||
var messageHandler = GetMessageHandler(method, out messageType);
|
|
||||||
|
|
||||||
add(messageType, messageHandler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected virtual MessageHandler GetMessageHandler(MethodInfo method, out Type messageType)
|
|
||||||
{
|
|
||||||
var parameters = method.GetParameters();
|
|
||||||
|
|
||||||
if (parameters.Length != 1 || !parameters[0].ParameterType.IsClass)
|
|
||||||
throw new ArgumentException($"Method {method.Name} does not have a single object parameter");
|
|
||||||
|
|
||||||
messageType = parameters[0].ParameterType;
|
|
||||||
var messageHandler = new MessageHandler();
|
|
||||||
|
|
||||||
if (method.ReturnType == typeof(void))
|
|
||||||
messageHandler.Action = CreateSyncMessageHandler(method);
|
|
||||||
else if (method.ReturnType == typeof(Task))
|
|
||||||
messageHandler.Action = CreateAsyncMessageHandler(method);
|
|
||||||
else
|
|
||||||
throw new ArgumentException($"Method {method.Name} needs to return void or a Task");
|
|
||||||
|
|
||||||
var exchangeAttribute = method.GetCustomAttribute<ExchangeAttribute>() ?? method.DeclaringType.GetCustomAttribute<ExchangeAttribute>();
|
|
||||||
messageHandler.Exchange = exchangeAttribute?.Name;
|
|
||||||
|
|
||||||
return messageHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected IEnumerable<Type> GetMessageTypes()
|
|
||||||
{
|
|
||||||
return messageHandlers.Keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected IEnumerable<string> GetMessageExchanges(Type type)
|
|
||||||
{
|
|
||||||
var exchanges = messageHandlers[type]
|
|
||||||
.Where(h => h.Exchange != null)
|
|
||||||
.Select(h => h.Exchange)
|
|
||||||
.Distinct(StringComparer.InvariantCulture)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
return exchanges.Length > 0 ? exchanges : new[] { defaultExchange };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public abstract string BindQueue(IModel channel);
|
|
||||||
|
|
||||||
|
|
||||||
public bool Accept(object message)
|
|
||||||
{
|
|
||||||
return messageHandlers.ContainsKey(message.GetType());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Task Visit(object message)
|
|
||||||
{
|
|
||||||
var registeredHandlers = messageHandlers[message.GetType()];
|
|
||||||
if (registeredHandlers != null)
|
|
||||||
return Task.WhenAll(registeredHandlers.Select(messageHandler => messageHandler.Action(message)));
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected virtual MessageHandlerAction CreateSyncMessageHandler(MethodInfo method)
|
|
||||||
{
|
|
||||||
return message =>
|
|
||||||
{
|
|
||||||
var controller = controllerFactoryFactory().CreateController(controllerType);
|
|
||||||
method.Invoke(controller, new[] { message });
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected virtual MessageHandlerAction CreateAsyncMessageHandler(MethodInfo method)
|
|
||||||
{
|
|
||||||
return message =>
|
|
||||||
{
|
|
||||||
var controller = controllerFactoryFactory().CreateController(controllerType);
|
|
||||||
return (Task)method.Invoke(controller, new[] { message });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
using System;
|
|
||||||
using RabbitMQ.Client;
|
|
||||||
|
|
||||||
namespace Tapeti.Registration
|
|
||||||
{
|
|
||||||
public class ControllerDynamicQueueRegistration : AbstractControllerRegistration
|
|
||||||
{
|
|
||||||
private readonly Func<IRoutingKeyStrategy> routingKeyStrategyFactory;
|
|
||||||
|
|
||||||
|
|
||||||
public ControllerDynamicQueueRegistration(Func<IControllerFactory> controllerFactoryFactory, Func<IRoutingKeyStrategy> routingKeyStrategyFactory, Type controllerType, string defaultExchange)
|
|
||||||
: base(controllerFactoryFactory, controllerType, defaultExchange)
|
|
||||||
{
|
|
||||||
this.routingKeyStrategyFactory = routingKeyStrategyFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override string BindQueue(IModel channel)
|
|
||||||
{
|
|
||||||
var queue = channel.QueueDeclare();
|
|
||||||
|
|
||||||
foreach (var messageType in GetMessageTypes())
|
|
||||||
{
|
|
||||||
var routingKey = routingKeyStrategyFactory().GetRoutingKey(messageType);
|
|
||||||
|
|
||||||
foreach (var exchange in GetMessageExchanges(messageType))
|
|
||||||
channel.QueueBind(queue.QueueName, exchange, routingKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
return queue.QueueName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
using System;
|
|
||||||
using RabbitMQ.Client;
|
|
||||||
|
|
||||||
namespace Tapeti.Registration
|
|
||||||
{
|
|
||||||
public class ControllerQueueRegistration : AbstractControllerRegistration
|
|
||||||
{
|
|
||||||
private readonly string queueName;
|
|
||||||
|
|
||||||
public ControllerQueueRegistration(Func<IControllerFactory> controllerFactoryFactory, Type controllerType, string defaultExchange, string queueName) : base(controllerFactoryFactory, controllerType, defaultExchange)
|
|
||||||
{
|
|
||||||
this.queueName = queueName;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override string BindQueue(IModel channel)
|
|
||||||
{
|
|
||||||
return channel.QueueDeclarePassive(queueName).QueueName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
28
Tapeti.Saga/SagaBindingMiddleware.cs
Normal file
28
Tapeti.Saga/SagaBindingMiddleware.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Tapeti.Config;
|
||||||
|
|
||||||
|
namespace Tapeti.Saga
|
||||||
|
{
|
||||||
|
public class SagaBindingMiddleware : IBindingMiddleware
|
||||||
|
{
|
||||||
|
public void Handle(IBindingContext context, Action next)
|
||||||
|
{
|
||||||
|
foreach (var parameter in context.Parameters.Where(p =>
|
||||||
|
p.Info.ParameterType.IsGenericType &&
|
||||||
|
p.Info.ParameterType.GetGenericTypeDefinition() == typeof(ISaga<>)))
|
||||||
|
{
|
||||||
|
parameter.SetBinding(messageContext =>
|
||||||
|
{
|
||||||
|
object saga;
|
||||||
|
if (!messageContext.Items.TryGetValue("Saga", out saga))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return saga.GetType() == typeof(ISaga<>) ? saga : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
Tapeti.Saga/SagaMemoryStore.cs
Normal file
43
Tapeti.Saga/SagaMemoryStore.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Tapeti.Saga
|
||||||
|
{
|
||||||
|
public class SagaMemoryStore : ISagaStore
|
||||||
|
{
|
||||||
|
private ISagaStore decoratedStore;
|
||||||
|
private readonly Dictionary<string, object> values = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
|
||||||
|
// Not a constructor to allow standard injection to work when using only the MemoryStore
|
||||||
|
public static SagaMemoryStore AsCacheFor(ISagaStore store)
|
||||||
|
{
|
||||||
|
return new SagaMemoryStore
|
||||||
|
{
|
||||||
|
decoratedStore = store
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<object> Read(string sagaId)
|
||||||
|
{
|
||||||
|
object value;
|
||||||
|
|
||||||
|
// ReSharper disable once InvertIf
|
||||||
|
if (!values.TryGetValue(sagaId, out value) && decoratedStore != null)
|
||||||
|
{
|
||||||
|
value = await decoratedStore.Read(sagaId);
|
||||||
|
values.Add(sagaId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Update(string sagaId, object state)
|
||||||
|
{
|
||||||
|
values[sagaId] = state;
|
||||||
|
if (decoratedStore != null)
|
||||||
|
await decoratedStore.Update(sagaId, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Tapeti.Saga/SagaMessageMiddleware.cs
Normal file
22
Tapeti.Saga/SagaMessageMiddleware.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using Tapeti.Config;
|
||||||
|
|
||||||
|
namespace Tapeti.Saga
|
||||||
|
{
|
||||||
|
public class SagaMessageMiddleware : IMessageMiddleware
|
||||||
|
{
|
||||||
|
private readonly IDependencyResolver dependencyResolver;
|
||||||
|
|
||||||
|
|
||||||
|
public SagaMessageMiddleware(IDependencyResolver dependencyResolver)
|
||||||
|
{
|
||||||
|
this.dependencyResolver = dependencyResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(IMessageContext context, Action next)
|
||||||
|
{
|
||||||
|
context.Items["Saga"] = dependencyResolver.Resolve<ISagaProvider>().Continue("");
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
Tapeti.Saga/SagaMiddleware.cs
Normal file
16
Tapeti.Saga/SagaMiddleware.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Tapeti.Config;
|
||||||
|
|
||||||
|
namespace Tapeti.Saga
|
||||||
|
{
|
||||||
|
public class SagaMiddleware : IMiddlewareBundle
|
||||||
|
{
|
||||||
|
public IEnumerable<object> GetContents(IDependencyResolver dependencyResolver)
|
||||||
|
{
|
||||||
|
(dependencyResolver as IDependencyInjector)?.RegisterDefault<ISagaProvider, SagaProvider>();
|
||||||
|
|
||||||
|
yield return new SagaBindingMiddleware();
|
||||||
|
yield return new SagaMessageMiddleware(dependencyResolver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
Tapeti.Saga/SagaProvider.cs
Normal file
90
Tapeti.Saga/SagaProvider.cs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Tapeti.Saga
|
||||||
|
{
|
||||||
|
public class SagaProvider : ISagaProvider
|
||||||
|
{
|
||||||
|
protected static readonly ConcurrentDictionary<string, SemaphoreSlim> SagaLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||||
|
private readonly ISagaStore store;
|
||||||
|
|
||||||
|
public SagaProvider(ISagaStore store)
|
||||||
|
{
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<ISaga<T>> Begin<T>(T initialState) where T : class
|
||||||
|
{
|
||||||
|
var saga = await Saga<T>.Create(() => Task.FromResult(initialState));
|
||||||
|
await store.Update(saga.Id, saga.State);
|
||||||
|
|
||||||
|
return saga;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ISaga<T>> Continue<T>(string sagaId) where T : class
|
||||||
|
{
|
||||||
|
return await Saga<T>.Create(async () => await store.Read(sagaId) as T, sagaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<object> Continue(string sagaId)
|
||||||
|
{
|
||||||
|
return new Saga<object>
|
||||||
|
{
|
||||||
|
Id = sagaId,
|
||||||
|
State = await store.Read(sagaId)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected class Saga<T> : ISaga<T> where T : class
|
||||||
|
{
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
|
public T State { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public static async Task<Saga<T>> Create(Func<Task<T>> getState, string id = null)
|
||||||
|
{
|
||||||
|
var sagaId = id ?? Guid.NewGuid().ToString();
|
||||||
|
await SagaLocks.GetOrAdd(sagaId, new SemaphoreSlim(1)).WaitAsync();
|
||||||
|
|
||||||
|
var saga = new Saga<T>
|
||||||
|
{
|
||||||
|
Id = sagaId,
|
||||||
|
State = await getState()
|
||||||
|
};
|
||||||
|
|
||||||
|
return saga;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SemaphoreSlim semaphore;
|
||||||
|
if (SagaLocks.TryGetValue(Id, out semaphore))
|
||||||
|
semaphore.Release();
|
||||||
|
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void ExpectResponse(string callId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void ResolveResponse(string callId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
// General Information about an assembly is controlled through the following
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using SimpleInjector;
|
|
||||||
|
|
||||||
namespace Tapeti.SimpleInjector
|
|
||||||
{
|
|
||||||
public class SimpleInjectorControllerFactory : IControllerFactory
|
|
||||||
{
|
|
||||||
private readonly Container container;
|
|
||||||
|
|
||||||
|
|
||||||
public SimpleInjectorControllerFactory(Container container)
|
|
||||||
{
|
|
||||||
this.container = container;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public object CreateController(Type controllerType)
|
|
||||||
{
|
|
||||||
return container.GetInstance(controllerType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using SimpleInjector;
|
using SimpleInjector;
|
||||||
using Tapeti.Annotations;
|
|
||||||
using Tapeti.Default;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Tapeti.SimpleInjector
|
namespace Tapeti.SimpleInjector
|
||||||
{
|
{
|
||||||
public class SimpleInjectorDependencyResolver : IDependencyResolver, IDependencyInjector
|
public class SimpleInjectorDependencyResolver : IDependencyInjector
|
||||||
{
|
{
|
||||||
private readonly Container container;
|
private readonly Container container;
|
||||||
|
|
||||||
public SimpleInjectorDependencyResolver(Container container, bool registerDefaults = true)
|
public SimpleInjectorDependencyResolver(Container container)
|
||||||
{
|
{
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
|
||||||
if (registerDefaults)
|
|
||||||
RegisterDefaults();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public T Resolve<T>() where T : class
|
public T Resolve<T>() where T : class
|
||||||
@ -25,10 +18,25 @@ namespace Tapeti.SimpleInjector
|
|||||||
return container.GetInstance<T>();
|
return container.GetInstance<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object Resolve(Type type)
|
||||||
public void RegisterPublisher(IPublisher publisher)
|
|
||||||
{
|
{
|
||||||
IfUnregistered<IPublisher>(container.GetCurrentRegistrations(), () => container.RegisterSingleton(publisher));
|
return container.GetInstance(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void RegisterDefault<TService, TImplementation>() where TService : class where TImplementation : class, TService
|
||||||
|
{
|
||||||
|
// ReSharper disable once SimplifyLinqExpression - not a fan of negative predicates
|
||||||
|
if (!container.GetCurrentRegistrations().Any(ip => ip.ServiceType == typeof(TService)))
|
||||||
|
container.Register<TService, TImplementation>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void RegisterPublisher(Func<IPublisher> publisher)
|
||||||
|
{
|
||||||
|
// ReSharper disable once SimplifyLinqExpression - still not a fan of negative predicates
|
||||||
|
if (!container.GetCurrentRegistrations().Any(ip => ip.ServiceType == typeof(IPublisher)))
|
||||||
|
container.Register(publisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -36,32 +44,5 @@ namespace Tapeti.SimpleInjector
|
|||||||
{
|
{
|
||||||
container.Register(type);
|
container.Register(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public SimpleInjectorDependencyResolver RegisterDefaults()
|
|
||||||
{
|
|
||||||
var currentRegistrations = container.GetCurrentRegistrations();
|
|
||||||
|
|
||||||
IfUnregistered<IControllerFactory, SimpleInjectorControllerFactory>(currentRegistrations);
|
|
||||||
IfUnregistered<IMessageSerializer, DefaultMessageSerializer>(currentRegistrations);
|
|
||||||
IfUnregistered<IRoutingKeyStrategy, DefaultRoutingKeyStrategy>(currentRegistrations);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void IfUnregistered<TService, TImplementation>(IEnumerable<InstanceProducer> currentRegistrations) where TService : class where TImplementation: class, TService
|
|
||||||
{
|
|
||||||
// ReSharper disable once SimplifyLinqExpression - not a fan of negative predicates
|
|
||||||
if (!currentRegistrations.Any(ip => ip.ServiceType == typeof(TService)))
|
|
||||||
container.Register<TService, TImplementation>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void IfUnregistered<TService>(IEnumerable<InstanceProducer> currentRegistrations, Action register) where TService : class
|
|
||||||
{
|
|
||||||
// ReSharper disable once SimplifyLinqExpression - not a fan of negative predicates
|
|
||||||
if (!currentRegistrations.Any(ip => ip.ServiceType == typeof(TService)))
|
|
||||||
register();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,6 @@
|
|||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="SimpleInjectorControllerFactory.cs" />
|
|
||||||
<Compile Include="SimpleInjectorDependencyResolver.cs" />
|
<Compile Include="SimpleInjectorDependencyResolver.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -59,35 +59,38 @@
|
|||||||
<Compile Include="Connection\TapetiWorker.cs" />
|
<Compile Include="Connection\TapetiWorker.cs" />
|
||||||
<Compile Include="Default\ConsoleLogger.cs" />
|
<Compile Include="Default\ConsoleLogger.cs" />
|
||||||
<Compile Include="Default\DevNullLogger.cs" />
|
<Compile Include="Default\DevNullLogger.cs" />
|
||||||
|
<Compile Include="Helpers\ConsoleHelper.cs" />
|
||||||
|
<Compile Include="Helpers\MiddlewareHelper.cs" />
|
||||||
<Compile Include="IConnection.cs" />
|
<Compile Include="IConnection.cs" />
|
||||||
<Compile Include="ILogger.cs" />
|
<Compile Include="ILogger.cs" />
|
||||||
<Compile Include="ITopology.cs" />
|
<Compile Include="Config\IMessageContext.cs" />
|
||||||
|
<Compile Include="Default\BindingBufferStop.cs" />
|
||||||
|
<Compile Include="Config\IMessageMiddleware.cs" />
|
||||||
|
<Compile Include="Config\IMiddlewareBundle.cs" />
|
||||||
|
<Compile Include="Config\IConfig.cs" />
|
||||||
<Compile Include="MessageController.cs" />
|
<Compile Include="MessageController.cs" />
|
||||||
<Compile Include="TapetiConnectionBuilder.cs" />
|
<Compile Include="Config\IBindingMiddleware.cs" />
|
||||||
<Compile Include="TapetiConnectionParams.cs" />
|
<Compile Include="TapetiConnectionParams.cs" />
|
||||||
<Compile Include="TapetiTopologyBuilder.cs" />
|
<Compile Include="TapetiConfig.cs" />
|
||||||
<Compile Include="TapetiTypes.cs" />
|
<Compile Include="TapetiTypes.cs" />
|
||||||
<Compile Include="Tasks\SingleThreadTaskQueue.cs" />
|
<Compile Include="Tasks\SingleThreadTaskQueue.cs" />
|
||||||
<Compile Include="Default\DefaultControllerFactory.cs" />
|
|
||||||
<Compile Include="Default\DefaultDependencyResolver.cs" />
|
|
||||||
<Compile Include="Default\DefaultMessageSerializer.cs" />
|
<Compile Include="Default\DefaultMessageSerializer.cs" />
|
||||||
<Compile Include="Default\DefaultRoutingKeyStrategy.cs" />
|
<Compile Include="Default\DefaultRoutingKeyStrategy.cs" />
|
||||||
<Compile Include="IControllerFactory.cs" />
|
|
||||||
<Compile Include="IDependencyResolver.cs" />
|
<Compile Include="IDependencyResolver.cs" />
|
||||||
<Compile Include="IMessageSerializer.cs" />
|
<Compile Include="IMessageSerializer.cs" />
|
||||||
<Compile Include="IPublisher.cs" />
|
<Compile Include="IPublisher.cs" />
|
||||||
<Compile Include="IRoutingKeyStrategy.cs" />
|
<Compile Include="IRoutingKeyStrategy.cs" />
|
||||||
<Compile Include="IQueueRegistration.cs" />
|
|
||||||
<Compile Include="ISubscriber.cs" />
|
<Compile Include="ISubscriber.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Registration\AbstractControllerRegistration.cs" />
|
|
||||||
<Compile Include="Registration\ControllerDynamicQueueRegistration.cs" />
|
|
||||||
<Compile Include="Registration\ControllerQueueRegistration.cs" />
|
|
||||||
<Compile Include="TapetiConnection.cs" />
|
<Compile Include="TapetiConnection.cs" />
|
||||||
|
<Compile Include="Config\IBindingContext.cs" />
|
||||||
|
<Compile Include="Default\DependencyResolverBinding.cs" />
|
||||||
|
<Compile Include="Default\MessageBinding.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
400
TapetiConfig.cs
Normal file
400
TapetiConfig.cs
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Tapeti.Annotations;
|
||||||
|
using Tapeti.Config;
|
||||||
|
using Tapeti.Default;
|
||||||
|
using Tapeti.Helpers;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Tapeti
|
||||||
|
{
|
||||||
|
public class TopologyConfigurationException : Exception
|
||||||
|
{
|
||||||
|
public TopologyConfigurationException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate Task<object> MessageHandlerFunc(IMessageContext context, object message);
|
||||||
|
|
||||||
|
|
||||||
|
public class TapetiConfig
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, List<Binding>> staticRegistrations = new Dictionary<string, List<Binding>>();
|
||||||
|
private readonly Dictionary<Type, List<Binding>> dynamicRegistrations = new Dictionary<Type, List<Binding>>();
|
||||||
|
|
||||||
|
private readonly List<IBindingMiddleware> bindingMiddleware = new List<IBindingMiddleware>();
|
||||||
|
private readonly List<IMessageMiddleware> messageMiddleware = new List<IMessageMiddleware>();
|
||||||
|
|
||||||
|
private readonly string exchange;
|
||||||
|
private readonly IDependencyResolver dependencyResolver;
|
||||||
|
|
||||||
|
|
||||||
|
public TapetiConfig(string exchange, IDependencyResolver dependencyResolver)
|
||||||
|
{
|
||||||
|
this.exchange = exchange;
|
||||||
|
this.dependencyResolver = dependencyResolver;
|
||||||
|
|
||||||
|
Use(new BindingBufferStop());
|
||||||
|
Use(new DependencyResolverBinding(dependencyResolver));
|
||||||
|
Use(new MessageBinding());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public IConfig Build()
|
||||||
|
{
|
||||||
|
var dependencyInjector = dependencyResolver as IDependencyInjector;
|
||||||
|
if (dependencyInjector != null)
|
||||||
|
{
|
||||||
|
if (ConsoleHelper.IsAvailable())
|
||||||
|
dependencyInjector.RegisterDefault<ILogger, ConsoleLogger>();
|
||||||
|
else
|
||||||
|
dependencyInjector.RegisterDefault<ILogger, DevNullLogger>();
|
||||||
|
|
||||||
|
dependencyInjector.RegisterDefault<IMessageSerializer, DefaultMessageSerializer>();
|
||||||
|
dependencyInjector.RegisterDefault<IRoutingKeyStrategy, DefaultRoutingKeyStrategy>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var queues = new List<IQueue>();
|
||||||
|
queues.AddRange(staticRegistrations.Select(qb => new Queue(new QueueInfo { Dynamic = false, Name = qb.Key }, qb.Value)));
|
||||||
|
|
||||||
|
// Group all bindings with the same index into queues, this will
|
||||||
|
// ensure each message type is unique on their queue
|
||||||
|
var dynamicBindings = new List<List<Binding>>();
|
||||||
|
foreach (var bindings in dynamicRegistrations.Values)
|
||||||
|
{
|
||||||
|
while (dynamicBindings.Count < bindings.Count)
|
||||||
|
dynamicBindings.Add(new List<Binding>());
|
||||||
|
|
||||||
|
for (var bindingIndex = 0; bindingIndex < bindings.Count; bindingIndex++)
|
||||||
|
dynamicBindings[bindingIndex].Add(bindings[bindingIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
queues.AddRange(dynamicBindings.Select(bl => new Queue(new QueueInfo { Dynamic = true }, bl)));
|
||||||
|
|
||||||
|
return new Config(exchange, dependencyResolver, messageMiddleware, queues);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TapetiConfig Use(IBindingMiddleware handler)
|
||||||
|
{
|
||||||
|
bindingMiddleware.Add(handler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TapetiConfig Use(IMessageMiddleware handler)
|
||||||
|
{
|
||||||
|
messageMiddleware.Add(handler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TapetiConfig Use(IMiddlewareBundle bundle)
|
||||||
|
{
|
||||||
|
foreach (var middleware in bundle.GetContents(dependencyResolver))
|
||||||
|
{
|
||||||
|
// ReSharper disable once CanBeReplacedWithTryCastAndCheckForNull
|
||||||
|
if (middleware is IBindingMiddleware)
|
||||||
|
Use((IBindingMiddleware) middleware);
|
||||||
|
else if (middleware is IMessageMiddleware)
|
||||||
|
Use((IMessageMiddleware)middleware);
|
||||||
|
else
|
||||||
|
throw new ArgumentException($"Unsupported middleware implementation: {middleware.GetType().Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TapetiConfig RegisterController(Type controller)
|
||||||
|
{
|
||||||
|
var controllerQueueInfo = GetQueueInfo(controller);
|
||||||
|
|
||||||
|
foreach (var method in controller.GetMembers(BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.Where(m => m.MemberType == MemberTypes.Method && m.DeclaringType != typeof(object))
|
||||||
|
.Select(m => (MethodInfo)m))
|
||||||
|
{
|
||||||
|
var methodQueueInfo = GetQueueInfo(method) ?? controllerQueueInfo;
|
||||||
|
if (!methodQueueInfo.IsValid)
|
||||||
|
throw new TopologyConfigurationException($"Method {method.Name} or controller {controller.Name} requires a queue attribute");
|
||||||
|
|
||||||
|
var context = new BindingContext(method.GetParameters().Select(p => new BindingParameter(p)).ToList());
|
||||||
|
var messageHandler = GetMessageHandler(context, method);
|
||||||
|
|
||||||
|
var handlerInfo = new Binding
|
||||||
|
{
|
||||||
|
Controller = controller,
|
||||||
|
Method = method,
|
||||||
|
QueueInfo = methodQueueInfo,
|
||||||
|
MessageClass = context.MessageClass,
|
||||||
|
MessageHandler = messageHandler
|
||||||
|
};
|
||||||
|
|
||||||
|
if (methodQueueInfo.Dynamic.GetValueOrDefault())
|
||||||
|
AddDynamicRegistration(context, handlerInfo);
|
||||||
|
else
|
||||||
|
AddStaticRegistration(context, handlerInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TapetiConfig RegisterAllControllers(Assembly assembly)
|
||||||
|
{
|
||||||
|
foreach (var type in assembly.GetTypes().Where(t => t.IsDefined(typeof(MessageControllerAttribute))))
|
||||||
|
RegisterController(type);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TapetiConfig RegisterAllControllers()
|
||||||
|
{
|
||||||
|
return RegisterAllControllers(Assembly.GetCallingAssembly());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected MessageHandlerFunc GetMessageHandler(IBindingContext context, MethodInfo method)
|
||||||
|
{
|
||||||
|
MiddlewareHelper.Go(bindingMiddleware, (handler, next) => handler.Handle(context, next));
|
||||||
|
|
||||||
|
if (context.MessageClass == null)
|
||||||
|
throw new TopologyConfigurationException($"Method {method.Name} in controller {method.DeclaringType?.Name} does not resolve to a message class");
|
||||||
|
|
||||||
|
|
||||||
|
var invalidBindings = context.Parameters.Where(p => !p.HasBinding).ToList();
|
||||||
|
|
||||||
|
// ReSharper disable once InvertIf - doesn't make the flow clearer imo
|
||||||
|
if (invalidBindings.Count > 0)
|
||||||
|
{
|
||||||
|
var parameterNames = string.Join(", ", invalidBindings.Select(p => p.Info.Name));
|
||||||
|
throw new TopologyConfigurationException($"Method {method.Name} in controller {method.DeclaringType?.Name} has unknown parameters: {parameterNames}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return WrapMethod(method, context.Parameters.Select(p => ((IBindingParameterAccess)p).GetBinding()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected MessageHandlerFunc WrapMethod(MethodInfo method, IEnumerable<ValueFactory> parameters)
|
||||||
|
{
|
||||||
|
if (method.ReturnType == typeof(void))
|
||||||
|
return WrapNullMethod(method, parameters);
|
||||||
|
|
||||||
|
if (method.ReturnType == typeof(Task))
|
||||||
|
return WrapTaskMethod(method, parameters);
|
||||||
|
|
||||||
|
if (method.ReturnType == typeof(Task<>))
|
||||||
|
{
|
||||||
|
var genericArguments = method.GetGenericArguments();
|
||||||
|
if (genericArguments.Length != 1)
|
||||||
|
throw new ArgumentException($"Method {method.Name} in controller {method.DeclaringType?.Name} must have exactly one generic argument to Task<>");
|
||||||
|
|
||||||
|
if (!genericArguments[0].IsClass)
|
||||||
|
throw new ArgumentException($"Method {method.Name} in controller {method.DeclaringType?.Name} must have an object generic argument to Task<>");
|
||||||
|
|
||||||
|
return WrapGenericTaskMethod(method, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.ReturnType.IsClass)
|
||||||
|
return WrapObjectMethod(method, parameters);
|
||||||
|
|
||||||
|
throw new ArgumentException($"Method {method.Name} in controller {method.DeclaringType?.Name} has an invalid return type");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected MessageHandlerFunc WrapNullMethod(MethodInfo method, IEnumerable<ValueFactory> parameters)
|
||||||
|
{
|
||||||
|
return (context, message) =>
|
||||||
|
{
|
||||||
|
method.Invoke(context.Controller, parameters.Select(p => p(context)).ToArray());
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected MessageHandlerFunc WrapTaskMethod(MethodInfo method, IEnumerable<ValueFactory> parameters)
|
||||||
|
{
|
||||||
|
return async (context, message) =>
|
||||||
|
{
|
||||||
|
await (Task)method.Invoke(context.Controller, parameters.Select(p => p(context)).ToArray());
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected MessageHandlerFunc WrapGenericTaskMethod(MethodInfo method, IEnumerable<ValueFactory> parameters)
|
||||||
|
{
|
||||||
|
return (context, message) =>
|
||||||
|
{
|
||||||
|
return (Task<object>)method.Invoke(context.Controller, parameters.Select(p => p(context)).ToArray());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected MessageHandlerFunc WrapObjectMethod(MethodInfo method, IEnumerable<ValueFactory> parameters)
|
||||||
|
{
|
||||||
|
return (context, message) =>
|
||||||
|
{
|
||||||
|
return Task.FromResult(method.Invoke(context.Controller, parameters.Select(p => p(context)).ToArray()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void AddStaticRegistration(IBindingContext context, Binding binding)
|
||||||
|
{
|
||||||
|
if (staticRegistrations.ContainsKey(binding.QueueInfo.Name))
|
||||||
|
{
|
||||||
|
var existing = staticRegistrations[binding.QueueInfo.Name];
|
||||||
|
|
||||||
|
// Technically we could easily do multicasting, but it complicates exception handling and requeueing
|
||||||
|
if (existing.Any(h => h.MessageClass == binding.MessageClass))
|
||||||
|
throw new TopologyConfigurationException($"Multiple handlers for message class {binding.MessageClass.Name} in queue {binding.QueueInfo.Name}");
|
||||||
|
|
||||||
|
existing.Add(binding);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
staticRegistrations.Add(binding.QueueInfo.Name, new List<Binding> { binding });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void AddDynamicRegistration(IBindingContext context, Binding binding)
|
||||||
|
{
|
||||||
|
if (dynamicRegistrations.ContainsKey(context.MessageClass))
|
||||||
|
dynamicRegistrations[context.MessageClass].Add(binding);
|
||||||
|
else
|
||||||
|
dynamicRegistrations.Add(context.MessageClass, new List<Binding> { binding });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected QueueInfo GetQueueInfo(MemberInfo member)
|
||||||
|
{
|
||||||
|
var dynamicQueueAttribute = member.GetCustomAttribute<DynamicQueueAttribute>();
|
||||||
|
var staticQueueAttribute = member.GetCustomAttribute<StaticQueueAttribute>();
|
||||||
|
|
||||||
|
if (dynamicQueueAttribute != null && staticQueueAttribute != null)
|
||||||
|
throw new TopologyConfigurationException($"Cannot combine static and dynamic queue attributes on {member.Name}");
|
||||||
|
|
||||||
|
if (dynamicQueueAttribute != null)
|
||||||
|
return new QueueInfo { Dynamic = true };
|
||||||
|
|
||||||
|
if (staticQueueAttribute != null)
|
||||||
|
return new QueueInfo { Dynamic = false, Name = staticQueueAttribute.Name };
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected class QueueInfo
|
||||||
|
{
|
||||||
|
public bool? Dynamic { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public bool IsValid => Dynamic.HasValue || !string.IsNullOrEmpty(Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected class Config : IConfig
|
||||||
|
{
|
||||||
|
public string Exchange { get; }
|
||||||
|
public IDependencyResolver DependencyResolver { get; }
|
||||||
|
public IReadOnlyList<IMessageMiddleware> MessageMiddleware { get; }
|
||||||
|
public IEnumerable<IQueue> Queues { get; }
|
||||||
|
|
||||||
|
|
||||||
|
public Config(string exchange, IDependencyResolver dependencyResolver, IReadOnlyList<IMessageMiddleware> messageMiddleware, IEnumerable<IQueue> queues)
|
||||||
|
{
|
||||||
|
Exchange = exchange;
|
||||||
|
DependencyResolver = dependencyResolver;
|
||||||
|
MessageMiddleware = messageMiddleware;
|
||||||
|
Queues = queues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected class Queue : IQueue
|
||||||
|
{
|
||||||
|
public bool Dynamic { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
public IEnumerable<IBinding> Bindings { get; }
|
||||||
|
|
||||||
|
|
||||||
|
public Queue(QueueInfo queue, IEnumerable<IBinding> bindings)
|
||||||
|
{
|
||||||
|
Dynamic = queue.Dynamic.GetValueOrDefault();
|
||||||
|
Name = queue.Name;
|
||||||
|
Bindings = bindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected class Binding : IBinding
|
||||||
|
{
|
||||||
|
public Type Controller { get; set; }
|
||||||
|
public MethodInfo Method { get; set; }
|
||||||
|
public Type MessageClass { get; set; }
|
||||||
|
|
||||||
|
public QueueInfo QueueInfo { get; set; }
|
||||||
|
public MessageHandlerFunc MessageHandler { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public bool Accept(object message)
|
||||||
|
{
|
||||||
|
return message.GetType() == MessageClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task<object> Invoke(IMessageContext context, object message)
|
||||||
|
{
|
||||||
|
return MessageHandler(context, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal interface IBindingParameterAccess
|
||||||
|
{
|
||||||
|
ValueFactory GetBinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class BindingContext : IBindingContext
|
||||||
|
{
|
||||||
|
public Type MessageClass { get; set; }
|
||||||
|
public IReadOnlyList<IBindingParameter> Parameters { get; }
|
||||||
|
|
||||||
|
|
||||||
|
public BindingContext(IReadOnlyList<IBindingParameter> parameters)
|
||||||
|
{
|
||||||
|
Parameters = parameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class BindingParameter : IBindingParameter, IBindingParameterAccess
|
||||||
|
{
|
||||||
|
private ValueFactory binding;
|
||||||
|
|
||||||
|
public ParameterInfo Info { get; }
|
||||||
|
public bool HasBinding => binding != null;
|
||||||
|
|
||||||
|
|
||||||
|
public BindingParameter(ParameterInfo parameter)
|
||||||
|
{
|
||||||
|
Info = parameter;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueFactory GetBinding()
|
||||||
|
{
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetBinding(ValueFactory valueFactory)
|
||||||
|
{
|
||||||
|
binding = valueFactory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,72 +1,35 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Tapeti.Annotations;
|
using Tapeti.Config;
|
||||||
using Tapeti.Connection;
|
using Tapeti.Connection;
|
||||||
using Tapeti.Default;
|
|
||||||
using Tapeti.Registration;
|
|
||||||
|
|
||||||
namespace Tapeti
|
namespace Tapeti
|
||||||
{
|
{
|
||||||
public class TapetiConnection : IDisposable
|
public class TapetiConnection : IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly IConfig config;
|
||||||
public TapetiConnectionParams Params { get; set; }
|
public TapetiConnectionParams Params { get; set; }
|
||||||
|
|
||||||
public string PublishExchange { get; set; } = "";
|
|
||||||
public string SubscribeExchange { get; set; } = "";
|
|
||||||
|
|
||||||
|
|
||||||
public IDependencyResolver DependencyResolver
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (dependencyResolver == null)
|
|
||||||
DependencyResolver = new DefaultDependencyResolver();
|
|
||||||
|
|
||||||
return dependencyResolver;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
dependencyResolver = value;
|
|
||||||
|
|
||||||
var dependencyInjector = value as IDependencyInjector;
|
|
||||||
dependencyInjector?.RegisterPublisher(GetPublisher());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private IDependencyResolver dependencyResolver;
|
|
||||||
private readonly Lazy<List<IQueueRegistration>> registrations = new Lazy<List<IQueueRegistration>>();
|
|
||||||
private readonly Lazy<TapetiWorker> worker;
|
private readonly Lazy<TapetiWorker> worker;
|
||||||
|
|
||||||
|
|
||||||
public TapetiConnection()
|
public TapetiConnection(IConfig config)
|
||||||
{
|
{
|
||||||
worker = new Lazy<TapetiWorker>(() => new TapetiWorker(
|
this.config = config;
|
||||||
DependencyResolver.Resolve<IMessageSerializer>(),
|
(config.DependencyResolver as IDependencyInjector)?.RegisterPublisher(GetPublisher);
|
||||||
DependencyResolver.Resolve<IRoutingKeyStrategy>())
|
|
||||||
|
worker = new Lazy<TapetiWorker>(() => new TapetiWorker(config.DependencyResolver, config.MessageMiddleware)
|
||||||
{
|
{
|
||||||
ConnectionParams = Params ?? new TapetiConnectionParams(),
|
ConnectionParams = Params ?? new TapetiConnectionParams(),
|
||||||
PublishExchange = PublishExchange
|
Exchange = config.Exchange
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public TapetiConnection WithDependencyResolver(IDependencyResolver resolver)
|
|
||||||
{
|
|
||||||
DependencyResolver = resolver;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<ISubscriber> Subscribe()
|
public async Task<ISubscriber> Subscribe()
|
||||||
{
|
{
|
||||||
if (!registrations.IsValueCreated || registrations.Value.Count == 0)
|
|
||||||
throw new ArgumentException("No controllers registered");
|
|
||||||
|
|
||||||
var subscriber = new TapetiSubscriber(() => worker.Value);
|
var subscriber = new TapetiSubscriber(() => worker.Value);
|
||||||
await subscriber.BindQueues(registrations.Value);
|
await subscriber.BindQueues(config.Queues);
|
||||||
|
|
||||||
return subscriber;
|
return subscriber;
|
||||||
}
|
}
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Tapeti
|
|
||||||
{
|
|
||||||
public class TapetiConnectionBuilder
|
|
||||||
{
|
|
||||||
public IConnection Build()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public TapetiConnectionBuilder SetExchange(string exchange)
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public TapetiConnectionBuilder SetDependencyResolver(IDependencyResolver dependencyResolver)
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public TapetiConnectionBuilder SetTopology(ITopology topology)
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using Tapeti.Annotations;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Tapeti
|
|
||||||
{
|
|
||||||
public class TopologyConfigurationException : Exception
|
|
||||||
{
|
|
||||||
public TopologyConfigurationException(string message) : base(message) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class TapetiTopologyBuilder
|
|
||||||
{
|
|
||||||
private readonly List<HandlerRegistration> registrations = new List<HandlerRegistration>();
|
|
||||||
|
|
||||||
|
|
||||||
public ITopology Build()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public TapetiTopologyBuilder RegisterController(Type controller)
|
|
||||||
{
|
|
||||||
var controllerRegistration = GetAttributesRegistration(controller);
|
|
||||||
|
|
||||||
foreach (var method in controller.GetMembers(BindingFlags.Public | BindingFlags.Instance)
|
|
||||||
.Where(m => m.MemberType == MemberTypes.Method && m.DeclaringType != typeof(object))
|
|
||||||
.Select(m => (MethodInfo)m))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (queueAttribute.Dynamic)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(queueAttribute.Name))
|
|
||||||
throw new ArgumentException("Dynamic queue attributes must not have a Name");
|
|
||||||
|
|
||||||
registrations.Value.Add(new ControllerDynamicQueueRegistration(
|
|
||||||
DependencyResolver.Resolve<IControllerFactory>,
|
|
||||||
DependencyResolver.Resolve<IRoutingKeyStrategy>,
|
|
||||||
type, SubscribeExchange));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(queueAttribute.Name))
|
|
||||||
throw new ArgumentException("Non-dynamic queue attribute must have a Name");
|
|
||||||
|
|
||||||
registrations.Value.Add(new ControllerQueueRegistration(
|
|
||||||
DependencyResolver.Resolve<IControllerFactory>,
|
|
||||||
type, SubscribeExchange, queueAttribute.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
(DependencyResolver as IDependencyInjector)?.RegisterController(type);
|
|
||||||
*/
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public TapetiTopologyBuilder RegisterAllControllers(Assembly assembly)
|
|
||||||
{
|
|
||||||
foreach (var type in assembly.GetTypes().Where(t => t.IsDefined(typeof(MessageControllerAttribute))))
|
|
||||||
RegisterController(type);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public TapetiTopologyBuilder RegisterAllControllers()
|
|
||||||
{
|
|
||||||
return RegisterAllControllers(Assembly.GetCallingAssembly());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected HandlerRegistration GetAttributesRegistration(MemberInfo member)
|
|
||||||
{
|
|
||||||
var registration = new HandlerRegistration();
|
|
||||||
|
|
||||||
var dynamicQueueAttribute = member.GetCustomAttribute<DynamicQueueAttribute>();
|
|
||||||
var staticQueueAttribute = member.GetCustomAttribute<StaticQueueAttribute>();
|
|
||||||
|
|
||||||
if (dynamicQueueAttribute != null && staticQueueAttribute != null)
|
|
||||||
throw new TopologyConfigurationException($"Cannot combine static and dynamic queue attributes on {member.Name}");
|
|
||||||
|
|
||||||
if (dynamicQueueAttribute != null)
|
|
||||||
registration.Dynamic = true;
|
|
||||||
else if (staticQueueAttribute != null)
|
|
||||||
{
|
|
||||||
registration.Dynamic = false;
|
|
||||||
registration.QueueName = staticQueueAttribute.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return registration;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected class HandlerRegistration
|
|
||||||
{
|
|
||||||
public bool? Dynamic { get; set; }
|
|
||||||
public string QueueName { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected class Topology : ITopology
|
|
||||||
{
|
|
||||||
private readonly List<Queue> queues = new List<Queue>();
|
|
||||||
|
|
||||||
|
|
||||||
public void Add(Queue queue)
|
|
||||||
{
|
|
||||||
queues.Add(queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IQueue> Queues()
|
|
||||||
{
|
|
||||||
return queues;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected class Queue : IQueue
|
|
||||||
{
|
|
||||||
private readonly List<Binding> bindings = new List<Binding>();
|
|
||||||
|
|
||||||
|
|
||||||
public void Add(Binding binding)
|
|
||||||
{
|
|
||||||
bindings.Add(binding);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IBinding> Bindings()
|
|
||||||
{
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected class Binding : IBinding
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
// General Information about an assembly is controlled through the following
|
||||||
|
17
Test/Visualizer.cs
Normal file
17
Test/Visualizer.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class Visualizer
|
||||||
|
{
|
||||||
|
public void VisualizeMarco()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Marco!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void VisualizePolo()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Polo!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user