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.Diagnostics.Eventing.Reader;
using System.Collections.Generic;
using System.Linq;
using RabbitMQ.Client;
using Tapeti.Config;
using Tapeti.Helpers;
namespace Tapeti.Connection
{
public class TapetiConsumer : DefaultBasicConsumer
{
private readonly TapetiWorker worker;
private readonly IMessageSerializer messageSerializer;
private readonly IQueueRegistration queueRegistration;
private readonly IDependencyResolver dependencyResolver;
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.messageSerializer = messageSerializer;
this.queueRegistration = queueRegistration;
this.dependencyResolver = dependencyResolver;
this.messageMiddleware = messageMiddleware;
this.bindings = bindings.ToList();
}
@ -24,22 +29,46 @@ namespace Tapeti.Connection
{
try
{
var message = messageSerializer.Deserialize(body, properties);
var message = dependencyResolver.Resolve<IMessageSerializer>().Deserialize(body, properties);
if (message == null)
throw new ArgumentException("Empty message");
if (queueRegistration.Accept(message))
queueRegistration.Visit(message);
else
var handled = false;
foreach (var binding in bindings.Where(b => b.Accept(message)))
{
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}");
worker.Respond(deliveryTag, ConsumeResponse.Ack);
}
catch (Exception)
{
//TODO pluggable exception handling
worker.Respond(deliveryTag, ConsumeResponse.Nack);
}
worker.Respond(deliveryTag, ConsumeResponse.Requeue);
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.Linq;
using System.Threading.Tasks;
using Tapeti.Config;
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.Collections.Generic;
using System.Threading.Tasks;
using RabbitMQ.Client;
using RabbitMQ.Client.Exceptions;
using RabbitMQ.Client.Framing;
using Tapeti.Config;
using Tapeti.Tasks;
namespace Tapeti.Connection
@ -10,20 +12,23 @@ namespace Tapeti.Connection
public class TapetiWorker
{
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 IRoutingKeyStrategy routingKeyStrategy;
private readonly Lazy<SingleThreadTaskQueue> taskQueue = new Lazy<SingleThreadTaskQueue>();
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.routingKeyStrategy = routingKeyStrategy;
this.dependencyResolver = dependencyResolver;
this.messageMiddleware = messageMiddleware;
messageSerializer = dependencyResolver.Resolve<IMessageSerializer>();
routingKeyStrategy = dependencyResolver.Resolve<IRoutingKeyStrategy>();
}
@ -35,29 +40,45 @@ namespace Tapeti.Connection
var body = messageSerializer.Serialize(message, properties);
(await GetChannel())
.BasicPublish(PublishExchange, routingKeyStrategy.GetRoutingKey(message.GetType()), false,
.BasicPublish(Exchange, routingKeyStrategy.GetRoutingKey(message.GetType()), false,
properties, body);
}).Unwrap();
}
public Task Subscribe(string queueName, IQueueRegistration queueRegistration)
public Task Consume(string queueName, IEnumerable<IBinding> bindings)
{
return taskQueue.Value.Add(async () =>
{
(await GetChannel())
.BasicConsume(queueName, false, new TapetiConsumer(this, messageSerializer, queueRegistration));
(await GetChannel()).BasicConsume(queueName, false, new TapetiConsumer(this, dependencyResolver, bindings, messageMiddleware));
}).Unwrap();
}
public async Task Subscribe(IQueueRegistration registration)
public async Task Subscribe(IQueue queue)
{
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(() =>
{
if (channel != null)
if (channelInstance != null)
{
channel.Dispose();
channel = null;
channelInstance.Dispose();
channelInstance = null;
}
// ReSharper disable once InvertIf
@ -115,8 +136,8 @@ namespace Tapeti.Connection
/// </remarks>
private async Task<IModel> GetChannel()
{
if (channel != null)
return channel;
if (channelInstance != null)
return channelInstance;
var connectionFactory = new ConnectionFactory
{
@ -134,7 +155,7 @@ namespace Tapeti.Connection
try
{
connection = connectionFactory.CreateConnection();
channel = connection.CreateModel();
channelInstance = connection.CreateModel();
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
{
T Resolve<T>() where T : class;
object Resolve(Type type);
}
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);
}
}

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
{

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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 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.Linq;
using System.Reflection;
using SimpleInjector;
using Tapeti.Annotations;
using Tapeti.Default;
using System.Collections.Generic;
namespace Tapeti.SimpleInjector
{
public class SimpleInjectorDependencyResolver : IDependencyResolver, IDependencyInjector
public class SimpleInjectorDependencyResolver : IDependencyInjector
{
private readonly Container container;
public SimpleInjectorDependencyResolver(Container container, bool registerDefaults = true)
public SimpleInjectorDependencyResolver(Container container)
{
this.container = container;
if (registerDefaults)
RegisterDefaults();
}
public T Resolve<T>() where T : class
@ -25,10 +18,25 @@ namespace Tapeti.SimpleInjector
return container.GetInstance<T>();
}
public void RegisterPublisher(IPublisher publisher)
public object Resolve(Type type)
{
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);
}
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" />
</ItemGroup>
<ItemGroup>
<Compile Include="SimpleInjectorControllerFactory.cs" />
<Compile Include="SimpleInjectorDependencyResolver.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View File

@ -59,35 +59,38 @@
<Compile Include="Connection\TapetiWorker.cs" />
<Compile Include="Default\ConsoleLogger.cs" />
<Compile Include="Default\DevNullLogger.cs" />
<Compile Include="Helpers\ConsoleHelper.cs" />
<Compile Include="Helpers\MiddlewareHelper.cs" />
<Compile Include="IConnection.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="TapetiConnectionBuilder.cs" />
<Compile Include="Config\IBindingMiddleware.cs" />
<Compile Include="TapetiConnectionParams.cs" />
<Compile Include="TapetiTopologyBuilder.cs" />
<Compile Include="TapetiConfig.cs" />
<Compile Include="TapetiTypes.cs" />
<Compile Include="Tasks\SingleThreadTaskQueue.cs" />
<Compile Include="Default\DefaultControllerFactory.cs" />
<Compile Include="Default\DefaultDependencyResolver.cs" />
<Compile Include="Default\DefaultMessageSerializer.cs" />
<Compile Include="Default\DefaultRoutingKeyStrategy.cs" />
<Compile Include="IControllerFactory.cs" />
<Compile Include="IDependencyResolver.cs" />
<Compile Include="IMessageSerializer.cs" />
<Compile Include="IPublisher.cs" />
<Compile Include="IRoutingKeyStrategy.cs" />
<Compile Include="IQueueRegistration.cs" />
<Compile Include="ISubscriber.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="Config\IBindingContext.cs" />
<Compile Include="Default\DependencyResolverBinding.cs" />
<Compile Include="Default\MessageBinding.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.

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.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Tapeti.Annotations;
using Tapeti.Config;
using Tapeti.Connection;
using Tapeti.Default;
using Tapeti.Registration;
namespace Tapeti
{
public class TapetiConnection : IDisposable
{
private readonly IConfig config;
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;
public TapetiConnection()
public TapetiConnection(IConfig config)
{
worker = new Lazy<TapetiWorker>(() => new TapetiWorker(
DependencyResolver.Resolve<IMessageSerializer>(),
DependencyResolver.Resolve<IRoutingKeyStrategy>())
this.config = config;
(config.DependencyResolver as IDependencyInjector)?.RegisterPublisher(GetPublisher);
worker = new Lazy<TapetiWorker>(() => new TapetiWorker(config.DependencyResolver, config.MessageMiddleware)
{
ConnectionParams = Params ?? new TapetiConnectionParams(),
PublishExchange = PublishExchange
Exchange = config.Exchange
});
}
public TapetiConnection WithDependencyResolver(IDependencyResolver resolver)
{
DependencyResolver = resolver;
return this;
}
public async Task<ISubscriber> Subscribe()
{
if (!registrations.IsValueCreated || registrations.Value.Count == 0)
throw new ArgumentException("No controllers registered");
var subscriber = new TapetiSubscriber(() => worker.Value);
await subscriber.BindQueues(registrations.Value);
await subscriber.BindQueues(config.Queues);
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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 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!");
}
}
}