1
0
mirror of synced 2024-11-21 17:03:50 +00:00

Back to a working state

This commit is contained in:
Mark van Renswoude 2016-12-11 15:08:58 +01:00
parent f5ae7322bd
commit cb06ffd1b8
41 changed files with 956 additions and 709 deletions

24
Config/IBindingContext.cs Normal file
View 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);
}
}

View 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
View 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
View 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; }
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace Tapeti.Config
{
public interface IMessageMiddleware
{
void Handle(IMessageContext context, Action next);
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Tapeti.Config
{
public interface IMiddlewareBundle
{
IEnumerable<object> GetContents(IDependencyResolver dependencyResolver);
}
}

View File

@ -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>();
}
} }
} }

View File

@ -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());
} }
} }
} }

View File

@ -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;
} }
} }
} }

View 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)
{
}
}
}

View File

@ -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");
}
}
}

View File

@ -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);
}
}
}

View 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
View 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
View 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;
}
}
}
}

View 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);
}
}
}

View File

@ -1,9 +0,0 @@
using System;
namespace Tapeti
{
public interface IControllerFactory
{
object CreateController(Type controllerType);
}
}

View File

@ -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);
} }
} }

View File

@ -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);
}
}

View File

@ -1,6 +1,4 @@
using System; namespace Tapeti
namespace Tapeti
{ {
public interface ISubscriber public interface ISubscriber
{ {

View File

@ -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
{
}
}

View File

@ -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

View File

@ -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 });
};
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View 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();
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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);
}
}
}

View 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();
}
}
}
}

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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();
}
} }
} }

View File

@ -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>

View File

@ -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
View 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;
}
}
}
}

View File

@ -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;
} }

View File

@ -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;
}
}
}

View File

@ -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
{
}
}
}

View File

@ -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
View File

@ -0,0 +1,17 @@
using System;
namespace Test
{
public class Visualizer
{
public void VisualizeMarco()
{
Console.WriteLine("Marco!");
}
public void VisualizePolo()
{
Console.WriteLine("Polo!");
}
}
}