MAX-911 RDB Relaties samenvoegen vanuit LEF en update ontvangen in LEF

MAX-1081 POC Dictionary tasks in Web voor request
Setup voor Transient request response met TODO's
This commit is contained in:
Menno van Lavieren 2019-04-24 18:04:30 +02:00
parent 9bb192c067
commit 6bc6cfe216
17 changed files with 388 additions and 20 deletions

View File

@ -105,9 +105,9 @@ namespace Tapeti.Flow.Default
// TODO disallow if replyto is not specified?
if (reply.ReplyTo != null)
await publisher.PublishDirect(message, reply.ReplyTo, properties, true);
await publisher.PublishDirect(message, reply.ReplyTo, properties, reply.Mandatory);
else
await publisher.Publish(message, properties, true);
await publisher.Publish(message, properties, reply.Mandatory);
await context.Delete();
}
@ -129,8 +129,8 @@ namespace Tapeti.Flow.Default
throw new ArgumentException("responseHandler must be a registered message handler", nameof(responseHandler));
var requestAttribute = request.GetType().GetCustomAttribute<RequestAttribute>();
if (requestAttribute?.Response != null && requestAttribute.Response != binding.MessageClass)
throw new ArgumentException($"responseHandler must accept message of type {binding.MessageClass}", nameof(responseHandler));
if (requestAttribute?.Response != null && !binding.Accept(requestAttribute.Response))
throw new ArgumentException($"responseHandler must accept message of type {requestAttribute.Response}", nameof(responseHandler));
var continuationAttribute = binding.Method.GetCustomAttribute<ContinuationAttribute>();
if (continuationAttribute == null)
@ -157,7 +157,8 @@ namespace Tapeti.Flow.Default
{
CorrelationId = context.Properties.CorrelationId,
ReplyTo = context.Properties.ReplyTo,
ResponseTypeName = requestAttribute.Response.FullName
ResponseTypeName = requestAttribute.Response.FullName,
Mandatory = context.Properties.Persistent
};
}

View File

@ -57,6 +57,8 @@ namespace Tapeti.Flow.Default
public string CorrelationId { get; set; }
public string ResponseTypeName { get; set; }
public bool Mandatory { get; set; }
public ReplyMetadata Clone()
{
@ -64,7 +66,8 @@ namespace Tapeti.Flow.Default
{
ReplyTo = ReplyTo,
CorrelationId = CorrelationId,
ResponseTypeName = ResponseTypeName
ResponseTypeName = ResponseTypeName,
Mandatory = Mandatory
};
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
using Tapeti.Config;
namespace Tapeti.Tests
{
public class TransientFilterMiddleware : IMessageFilterMiddleware
{
public Task Handle(IMessageContext context, Func<Task> next)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace Tapeti.Transient
{
public static class ConfigExtensions
{
public static TapetiConfig WithTransient(this TapetiConfig config, TimeSpan defaultTimeout, string dynamicQueuePrefix = "transient")
{
config.Use(new TransientMiddleware(defaultTimeout, dynamicQueuePrefix));
return config;
}
}
}

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Tapeti.Transient
{
public interface ITransientPublisher
{
Task<TResponse> RequestResponse<TRequest, TResponse>(TRequest request);
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Tapeti\Tapeti.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,52 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using Tapeti.Config;
namespace Tapeti.Transient
{
public class TransientGenericBinding : ICustomBinding
{
private readonly TransientRouter router;
public TransientGenericBinding(TransientRouter router, string dynamicQueuePrefix)
{
this.router = router;
DynamicQueuePrefix = dynamicQueuePrefix;
Method = typeof(TransientRouter).GetMethod("GenericHandleResponse");
}
public Type Controller => typeof(TransientRouter);
public MethodInfo Method { get; }
public QueueBindingMode QueueBindingMode => QueueBindingMode.DirectToQueue;
public string StaticQueueName => null;
public string DynamicQueuePrefix { get; }
public Type MessageClass => null;
public bool Accept(Type messageClass)
{
return true;
}
public bool Accept(IMessageContext context, object message)
{
return true;
}
public Task Invoke(IMessageContext context, object message)
{
router.GenericHandleResponse(message, context);
return Task.CompletedTask;
}
public void SetQueueName(string queueName)
{
router.TransientResponseQueueName = queueName;
}
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using Tapeti.Config;
namespace Tapeti.Transient
{
public class TransientMiddleware : ITapetiExtension, ITapetiExtentionBinding
{
private string dynamicQueuePrefix;
private TimeSpan defaultTimeout;
public TransientMiddleware(TimeSpan defaultTimeout, string dynamicQueuePrefix)
{
this.dynamicQueuePrefix = dynamicQueuePrefix;
this.defaultTimeout = defaultTimeout;
}
public void RegisterDefaults(IDependencyContainer container)
{
container.RegisterDefaultSingleton(() => new TransientRouter(container.Resolve<IInternalPublisher>(), defaultTimeout));
container.RegisterDefault<ITransientPublisher, TransientPublisher>();
}
public IEnumerable<object> GetMiddleware(IDependencyResolver dependencyResolver)
{
return new object[0];
}
public IEnumerable<ICustomBinding> GetBindings(IDependencyResolver dependencyResolver)
{
yield return new TransientGenericBinding(dependencyResolver.Resolve<TransientRouter>(), dynamicQueuePrefix);
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Tapeti.Transient
{
public class TransientPublisher : ITransientPublisher
{
private readonly TransientRouter router;
public TransientPublisher(TransientRouter router)
{
this.router = router;
}
public async Task<TResponse> RequestResponse<TRequest, TResponse>(TRequest request)
{
return (TResponse)(await router.RequestResponse(request));
}
}
}

View File

@ -0,0 +1,74 @@
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Threading;
using RabbitMQ.Client.Framing;
using Tapeti.Config;
namespace Tapeti.Transient
{
public class TransientRouter
{
private readonly TimeSpan defaultTimeout;
private readonly ConcurrentDictionary<Guid, TaskCompletionSource<object>> map = new ConcurrentDictionary<Guid, TaskCompletionSource<object>>();
private readonly IInternalPublisher internalPublisher;
public string TransientResponseQueueName { get; set; }
public TransientRouter(IInternalPublisher internalPublisher, TimeSpan defaultTimeout)
{
this.internalPublisher = internalPublisher;
this.defaultTimeout = defaultTimeout;
}
public void GenericHandleResponse(object response, IMessageContext context)
{
if (context.Properties.CorrelationId == null)
return;
if (!Guid.TryParse(context.Properties.CorrelationId, out var continuationID))
return;
if (map.TryRemove(continuationID, out var tcs))
tcs.SetResult(response);
}
public async Task<object> RequestResponse(object request)
{
var correlation = Guid.NewGuid();
var tcs = map.GetOrAdd(correlation, c => new TaskCompletionSource<object>());
try
{
var properties = new BasicProperties
{
CorrelationId = correlation.ToString(),
ReplyTo = TransientResponseQueueName,
Persistent = false
};
await internalPublisher.Publish(request, properties, false);
}
catch (Exception)
{
// Simple cleanup of the task and map dictionary.
if (map.TryRemove(correlation, out tcs))
tcs.TrySetResult(null);
throw;
}
using (new Timer(TimeoutResponse, tcs, defaultTimeout, TimeSpan.MaxValue))
{
return await tcs.Task;
}
}
private void TimeoutResponse(object tcs)
{
((TaskCompletionSource<object>)tcs).SetException(new TimeoutException("Transient RequestResponse timed out at " + defaultTimeout));
}
}
}

View File

@ -19,7 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Tests", "Tapeti.Tests\Tapeti.Tests.csproj", "{334F3715-63CF-4D13-B09A-38E2A616D4F5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tapeti.Serilog", "Tapeti.Serilog\Tapeti.Serilog.csproj", "{43AA5DF3-49D5-4795-A290-D6511502B564}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Serilog", "Tapeti.Serilog\Tapeti.Serilog.csproj", "{43AA5DF3-49D5-4795-A290-D6511502B564}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tapeti.Transient", "Tapeti.Transient\Tapeti.Transient.csproj", "{A6355E63-19AB-47EA-91FA-49B5E9B41F88}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -63,6 +65,10 @@ Global
{43AA5DF3-49D5-4795-A290-D6511502B564}.Debug|Any CPU.Build.0 = Debug|Any CPU
{43AA5DF3-49D5-4795-A290-D6511502B564}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43AA5DF3-49D5-4795-A290-D6511502B564}.Release|Any CPU.Build.0 = Release|Any CPU
{A6355E63-19AB-47EA-91FA-49B5E9B41F88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6355E63-19AB-47EA-91FA-49B5E9B41F88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6355E63-19AB-47EA-91FA-49B5E9B41F88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6355E63-19AB-47EA-91FA-49B5E9B41F88}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -46,6 +46,7 @@ namespace Tapeti.Config
IReadOnlyList<IMessageFilterMiddleware> MessageFilterMiddleware { get; }
IReadOnlyList<IMessageMiddleware> MessageMiddleware { get; }
bool Accept(Type messageClass);
bool Accept(IMessageContext context, object message);
Task Invoke(IMessageContext context, object message);
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Tapeti.Config
{
public interface ICustomBinding
{
Type Controller { get; }
MethodInfo Method { get; }
QueueBindingMode QueueBindingMode { get; }
string StaticQueueName { get; }
string DynamicQueuePrefix { get; }
Type MessageClass { get; } // Needed to get routing key information when QueueBindingMode = RoutingKey
bool Accept(Type messageClass);
bool Accept(IMessageContext context, object message);
Task Invoke(IMessageContext context, object message);
void SetQueueName(string queueName);
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Tapeti.Config
{
public interface ITapetiExtentionBinding
{
IEnumerable<ICustomBinding> GetBindings(IDependencyResolver dependencyResolver);
}
}

View File

@ -109,6 +109,9 @@ namespace Tapeti.Connection
{
if (binding.QueueBindingMode == QueueBindingMode.RoutingKey)
{
if (binding.MessageClass == null)
throw new NullReferenceException("Binding with QueueBindingMode = RoutingKey must have a MessageClass");
var routingKey = routingKeyStrategy.GetRoutingKey(binding.MessageClass);
var exchange = exchangeStrategy.GetExchange(binding.MessageClass);

View File

@ -22,8 +22,8 @@ namespace Tapeti
public class TapetiConfig
{
private readonly Dictionary<string, List<Binding>> staticRegistrations = new Dictionary<string, List<Binding>>();
private readonly Dictionary<string, Dictionary<Type, List<Binding>>> dynamicRegistrations = new Dictionary<string, Dictionary<Type, List<Binding>>>();
private readonly Dictionary<string, List<IBinding>> staticRegistrations = new Dictionary<string, List<IBinding>>();
private readonly Dictionary<string, Dictionary<Type, List<IBinding>>> dynamicRegistrations = new Dictionary<string, Dictionary<Type, List<IBinding>>>();
private readonly List<IBindingMiddleware> bindingMiddleware = new List<IBindingMiddleware>();
private readonly List<IMessageMiddleware> messageMiddleware = new List<IMessageMiddleware>();
@ -79,12 +79,12 @@ namespace Tapeti
//
foreach (var prefixGroup in dynamicRegistrations)
{
var dynamicBindings = new List<List<Binding>>();
var dynamicBindings = new List<List<IBinding>>();
foreach (var bindings in prefixGroup.Value.Values)
{
while (dynamicBindings.Count < bindings.Count)
dynamicBindings.Add(new List<Binding>());
dynamicBindings.Add(new List<IBinding>());
for (var bindingIndex = 0; bindingIndex < bindings.Count; bindingIndex++)
dynamicBindings[bindingIndex].Add(bindings[bindingIndex]);
@ -144,6 +144,8 @@ namespace Tapeti
var middlewareBundle = extension.GetMiddleware(dependencyResolver);
(extension as ITapetiExtentionBinding)?.GetBindings(dependencyResolver);
// ReSharper disable once InvertIf
if (middlewareBundle != null)
{
@ -212,7 +214,8 @@ namespace Tapeti
{
var controllerQueueInfo = GetQueueInfo(controller);
(dependencyResolver as IDependencyContainer)?.RegisterController(controller);
if (!controller.IsInterface)
(dependencyResolver as IDependencyContainer)?.RegisterController(controller);
foreach (var method in controller.GetMembers(BindingFlags.Public | BindingFlags.Instance)
.Where(m => m.MemberType == MemberTypes.Method && m.DeclaringType != typeof(object) && (m as MethodInfo)?.IsSpecialName == false)
@ -359,7 +362,7 @@ namespace Tapeti
}
protected void AddStaticRegistration(IBindingContext context, Binding binding)
protected void AddStaticRegistration(IBindingContext context, IBindingQueueInfo binding)
{
if (staticRegistrations.ContainsKey(binding.QueueInfo.Name))
{
@ -372,23 +375,23 @@ namespace Tapeti
existing.Add(binding);
}
else
staticRegistrations.Add(binding.QueueInfo.Name, new List<Binding> { binding });
staticRegistrations.Add(binding.QueueInfo.Name, new List<IBinding> { binding });
}
protected void AddDynamicRegistration(IBindingContext context, Binding binding)
protected void AddDynamicRegistration(IBindingContext context, IBindingQueueInfo binding)
{
var prefix = binding.QueueInfo.Name ?? "";
if (!dynamicRegistrations.TryGetValue(prefix, out Dictionary<Type, List<Binding>> prefixRegistrations))
if (!dynamicRegistrations.TryGetValue(prefix, out Dictionary<Type, List<IBinding>> prefixRegistrations))
{
prefixRegistrations = new Dictionary<Type, List<Binding>>();
prefixRegistrations = new Dictionary<Type, List<IBinding>>();
dynamicRegistrations.Add(prefix, prefixRegistrations);
}
if (!prefixRegistrations.TryGetValue(context.MessageClass, out List<Binding> bindings))
if (!prefixRegistrations.TryGetValue(context.MessageClass, out List<IBinding> bindings))
{
bindings = new List<Binding>();
bindings = new List<IBinding>();
prefixRegistrations.Add(context.MessageClass, bindings);
}
@ -491,8 +494,12 @@ namespace Tapeti
}
}
protected interface IBindingQueueInfo : IBuildBinding
{
QueueInfo QueueInfo { get; }
}
protected class Binding : IBuildBinding
protected class Binding : IBindingQueueInfo
{
public Type Controller { get; set; }
public MethodInfo Method { get; set; }
@ -523,6 +530,11 @@ namespace Tapeti
}
public bool Accept(Type messageClass)
{
return MessageClass.IsAssignableFrom(messageClass);
}
public bool Accept(IMessageContext context, object message)
{
return message.GetType() == MessageClass;
@ -536,6 +548,69 @@ namespace Tapeti
}
protected class CustomBinding : IBindingQueueInfo
{
private readonly ICustomBinding inner;
public CustomBinding(ICustomBinding inner)
{
this.inner = inner;
// Copy all variables to make them guaranteed readonly.
Controller = inner.Controller;
Method = inner.Method;
QueueBindingMode = inner.QueueBindingMode;
MessageClass = inner.MessageClass;
QueueInfo = inner.StaticQueueName != null
? new QueueInfo()
{
Dynamic = false,
Name = inner.StaticQueueName
}
: new QueueInfo()
{
Dynamic = true,
Name = inner.DynamicQueuePrefix
};
// Custom bindings cannot have other middleware messing with the binding.
MessageFilterMiddleware = new IMessageFilterMiddleware[0];
MessageMiddleware = new IMessageMiddleware[0];
}
public Type Controller { get; }
public MethodInfo Method { get; }
public string QueueName { get; private set; }
public QueueBindingMode QueueBindingMode { get; set; }
public IReadOnlyList<IMessageFilterMiddleware> MessageFilterMiddleware { get; }
public IReadOnlyList<IMessageMiddleware> MessageMiddleware { get; }
public bool Accept(Type messageClass)
{
return inner.Accept(messageClass);
}
public bool Accept(IMessageContext context, object message)
{
return inner.Accept(context, message);
}
public Task Invoke(IMessageContext context, object message)
{
return inner.Invoke(context, message);
}
public void SetQueueName(string queueName)
{
QueueName = queueName;
inner.SetQueueName(queueName);
}
public Type MessageClass { get; }
public QueueInfo QueueInfo { get; }
}
internal interface IBindingParameterAccess
{
ValueFactory GetBinding();

View File

@ -1,13 +1,21 @@
using System;
using System.Runtime.CompilerServices;
using SimpleInjector;
using Tapeti;
using Tapeti.DataAnnotations;
using Tapeti.Flow;
using Tapeti.SimpleInjector;
using System.Threading;
using Tapeti.Annotations;
namespace Test
{
public interface IDummy
{
[DynamicQueue("test1")]
void HandleMessage(PoloConfirmationResponseMessage msg);
}
internal class Program
{
private static void Main()
@ -25,6 +33,7 @@ namespace Test
.WithFlow()
.WithDataAnnotations()
.RegisterAllControllers()
.RegisterController(typeof(IDummy))
//.DisablePublisherConfirms() -> you probably never want to do this if you're using Flow or want requeues when a publish fails
.Build();