public async Task WhenMessageIsLockedByAnotherHandler_MessageWillBeLeftInTheQueue() { var messageLock = new FakeMessageLock(false); var testResolver = new InMemoryServiceResolver(sc => sc .AddLogging(l => l.AddXUnit(_outputHelper)) .AddSingleton<IMessageLockAsync>(messageLock)); var monitor = new TrackingLoggingMonitor(LoggerFactory.Create(lf => lf.AddXUnit()).CreateLogger<TrackingLoggingMonitor>()); var handler = new InspectableHandler<OrderAccepted>(); var middleware = new HandlerMiddlewareBuilder(testResolver, testResolver) .UseExactlyOnce<OrderAccepted>(nameof(InspectableHandler<OrderAccepted>), TimeSpan.FromSeconds(1)) .UseHandler(ctx => handler) .Build(); var context = TestHandleContexts.From<OrderAccepted>(); var result = await middleware.RunAsync(context, null, CancellationToken.None); handler.ReceivedMessages.ShouldBeEmpty(); result.ShouldBeFalse(); }
public async Task ThreeMiddlewares_ShouldExecuteInCorrectOrder() { var callRecord = new List <string>(); void Before(string id) => callRecord.Add($"Before_{id}"); void After(string id) => callRecord.Add($"After_{id}"); var outer = new TrackingMiddleware("outer", Before, After); var middle = new TrackingMiddleware("middle", Before, After); var inner = new TrackingMiddleware("inner", Before, After); var middleware = new HandlerMiddlewareBuilder(_resolver, _resolver) .Configure(pipe => { pipe.Use(outer); pipe.Use(middle); pipe.Use(inner); }).Build(); var context = new HandleMessageContext(new SimpleMessage(), typeof(SimpleMessage), "a-fake-queue"); await middleware.RunAsync(context, ct => { callRecord.Add("HandledMessage"); return(Task.FromResult(true)); }, CancellationToken.None); var record = string.Join(Environment.NewLine, callRecord); record.ShouldMatchApproved(c => c.SubFolder("Approvals")); }
public async Task MiddlewareBuilder_WithoutDefaults_ShouldExecute() { var callRecord = new List <string>(); void Before(string id) => callRecord.Add($"Before_{id}"); void After(string id) => callRecord.Add($"After_{id}"); var outer = new TrackingMiddleware("outer", Before, After); var inner = new TrackingMiddleware("inner", Before, After); var handler = new InspectableHandler <SimpleMessage>(); var middlewareBuilder = new HandlerMiddlewareBuilder(_resolver, _resolver) .Configure(hmb => hmb.Use(outer) .Use(inner) .UseHandler(ctx => handler)); var handlerMiddleware = middlewareBuilder.Build(); var context = TestHandleContexts.From <SimpleMessage>(); await handlerMiddleware.RunAsync(context, null, CancellationToken.None); callRecord.ShouldBe(new[] { "Before_outer", "Before_inner", "After_inner", "After_outer" }); handler.ReceivedMessages.ShouldHaveSingleItem(); }
/// <summary> /// Adds an <see cref="ExactlyOnceMiddleware{T}"/> to the current pipeline. /// </summary> /// <param name="builder">The current <see cref="HandlerMiddlewareBuilder"/>.</param> /// <param name="lockKey">A unique key to identify this lock, e.g. the name of the handler.</param> /// <param name="lockDuration">The length of time to lock messages while handling them.</param> /// <typeparam name="TMessage">The type of the message that should be locked.</typeparam> /// <returns>The current <see cref="HandlerMiddlewareBuilder"/>.</returns> public static HandlerMiddlewareBuilder UseExactlyOnce <TMessage>( this HandlerMiddlewareBuilder builder, string lockKey, TimeSpan?lockDuration = null) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (string.IsNullOrEmpty(lockKey)) { throw new ArgumentException("Parameter cannot be null or empty.", nameof(lockKey)); } HandleMessageMiddleware CreateMiddleware() { var messageLock = builder.ServiceResolver.ResolveService <IMessageLockAsync>(); var logger = builder.ServiceResolver.ResolveService <ILogger <ExactlyOnceMiddleware <TMessage> > >(); return(new ExactlyOnceMiddleware <TMessage>(messageLock, lockDuration ?? TimeSpan.MaxValue, lockKey, logger)); } builder.Use(CreateMiddleware); return(builder); }
public static HandlerMiddlewareBuilder UseMessageContextAccessor(this HandlerMiddlewareBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } var contextAccessor = builder.ServiceResolver.ResolveService <IMessageContextAccessor>(); return(builder.Use(new MessageContextAccessorMiddleware(contextAccessor))); }
/// <summary> /// Adds an error handler to the pipeline that will call methods on the the <see cref="IMessageMonitor"/> /// registered in services. /// </summary> /// <param name="builder">The <see cref="HandlerMiddlewareBuilder"/> to add the middleware to.</param> /// <returns>The current <see cref="HandlerMiddlewareBuilder"/>.</returns> /// <exception cref="ArgumentNullException">When the <see cref="HandlerMiddlewareBuilder"/> is null.</exception> public static HandlerMiddlewareBuilder UseErrorHandler(this HandlerMiddlewareBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } IMessageMonitor monitor = builder.ServiceResolver.ResolveService <IMessageMonitor>(); return(builder.Use(new ErrorHandlerMiddleware(monitor))); }
/// <summary> /// Adds a <see cref="StopwatchMiddleware"/> to the current pipeline. /// </summary> /// <param name="builder">The current <see cref="HandlerMiddlewareBuilder"/>.</param> /// <param name="handlerType">The type of the handler that results should be reported against.</param> /// <returns>The current <see cref="HandlerMiddlewareBuilder"/>.</returns> /// <exception cref="ArgumentNullException"> /// <paramref name="builder"/> is <see langword="null"/>. /// </exception> public static HandlerMiddlewareBuilder UseStopwatch( this HandlerMiddlewareBuilder builder, Type handlerType) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } IMessageMonitor monitor = builder.ServiceResolver.ResolveService <IMessageMonitor>(); return(builder.Use(new StopwatchMiddleware(monitor, handlerType))); }
/// <inheritdoc /> void ISubscriptionBuilder <T> .Configure( JustSayingBus bus, IHandlerResolver handlerResolver, IServiceResolver serviceResolver, IVerifyAmazonQueues creator, IAwsClientFactoryProxy awsClientFactoryProxy, ILoggerFactory loggerFactory) { var logger = loggerFactory.CreateLogger <QueueSubscriptionBuilder <T> >(); var attachedQueueConfig = new QueueAddressConfiguration(); ConfigureReads?.Invoke(attachedQueueConfig); IAmazonSQS sqsClient = awsClientFactoryProxy .GetAwsClientFactory() .GetSqsClient(RegionEndpoint.GetBySystemName(_queueAddress.RegionName)); var queue = new QueueAddressQueue(_queueAddress, sqsClient); attachedQueueConfig.SubscriptionGroupName ??= queue.QueueName; attachedQueueConfig.Validate(); bus.AddQueue(attachedQueueConfig.SubscriptionGroupName, queue); logger.LogInformation( "Created SQS queue subscription for '{MessageType}' on '{QueueName}'", typeof(T), queue.QueueName); var resolutionContext = new HandlerResolutionContext(queue.QueueName); var proposedHandler = handlerResolver.ResolveHandler <T>(resolutionContext); if (proposedHandler == null) { throw new HandlerNotRegisteredWithContainerException( $"There is no handler for '{typeof(T)}' messages."); } var middlewareBuilder = new HandlerMiddlewareBuilder(handlerResolver, serviceResolver); var handlerMiddleware = middlewareBuilder .Configure(MiddlewareConfiguration ?? (b => b.UseDefaults <T>(proposedHandler.GetType()))) .Build(); bus.AddMessageMiddleware <T>(queue.QueueName, handlerMiddleware); logger.LogInformation( "Added a message handler for message type for '{MessageType}' on queue '{QueueName}'", typeof(T), queue.QueueName); }
/// <summary> /// Adds a <see cref="HandlerInvocationMiddleware{T}"/> to the current pipeline. /// </summary> /// <param name="builder">The current <see cref="HandlerMiddlewareBuilder"/>.</param> /// <param name="handler">A factory that creates <see cref="IHandlerAsync{T}"/> instances from /// a <see cref="HandlerResolutionContext"/>.</param> /// <typeparam name="TMessage">The type of the message that should be handled</typeparam> /// <returns>The current <see cref="HandlerMiddlewareBuilder"/>.</returns> /// <exception cref="ArgumentNullException"> /// <paramref name="builder"/> or <paramref name="handler"/> is <see langword="null"/>. /// </exception> public static HandlerMiddlewareBuilder UseHandler <TMessage>( this HandlerMiddlewareBuilder builder, Func <HandlerResolutionContext, IHandlerAsync <TMessage> > handler) where TMessage : Message { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } return(builder.Use(new HandlerInvocationMiddleware <TMessage>(handler))); }
/// <summary> /// If an <see cref="IMessageBackoffStrategy"/> has been registered in services, then this will create /// a <see cref="BackoffMiddleware"/> and add it to the pipeline. /// </summary> /// <param name="builder">The <see cref="HandlerMiddlewareBuilder"/> to add the middleware to.</param> /// <param name="backoffStrategy">The <see cref="IMessageBackoffStrategy"/> to use to determine message visibility timeouts.</param> /// <returns>The current <see cref="HandlerMiddlewareBuilder"/>.</returns> /// <exception cref="ArgumentNullException">When the <see cref="HandlerMiddlewareBuilder"/> is null.</exception> public static HandlerMiddlewareBuilder UseBackoff(this HandlerMiddlewareBuilder builder, IMessageBackoffStrategy backoffStrategy) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (backoffStrategy == null) { throw new ArgumentNullException(nameof(backoffStrategy)); } var loggerFactory = builder.ServiceResolver.ResolveService <ILoggerFactory>(); var monitor = builder.ServiceResolver.ResolveService <IMessageMonitor>(); return(builder.Use(new BackoffMiddleware(backoffStrategy, loggerFactory, monitor))); }
protected override void Given() { base.Given(); var handler = new InspectableHandler <SimpleMessage>(); var testResolver = new InMemoryServiceResolver(_outputHelper, _messageMonitor, sc => sc.AddSingleton <IHandlerAsync <SimpleMessage> >(handler)); var middleware = new HandlerMiddlewareBuilder(testResolver, testResolver) .UseBackoff(MessageBackoffStrategy) .UseDefaults <SimpleMessage>(handler.GetType()) .Build(); _middlewareMap.Add <OrderAccepted>(_queue.QueueName, middleware); }
protected override void Given() { _queue = CreateSuccessfulTestQueue(Guid.NewGuid().ToString(), new TestMessage()); Queues.Add(_queue); _messageLock = new FakeMessageLock(); var serviceResolver = new InMemoryServiceResolver(sc => sc.AddSingleton <IMessageLockAsync>(_messageLock) .AddSingleton <IHandlerAsync <SimpleMessage> >(Handler) .AddLogging(x => x.AddXUnit(OutputHelper))); var middlewareBuilder = new HandlerMiddlewareBuilder(serviceResolver, serviceResolver); var middleware = middlewareBuilder.Configure(pipe => { pipe.UseExactlyOnce <SimpleMessage>("a-unique-lock-key"); pipe.UseHandler <SimpleMessage>(); }).Build(); Middleware = middleware; }
protected override void Given() { base.Given(); var handler = new InspectableHandler <SimpleMessage>() { OnHandle = msg => throw _expectedException }; var testResolver = new InMemoryServiceResolver(_outputHelper, _messageMonitor, sc => sc.AddSingleton <IHandlerAsync <SimpleMessage> >(handler)); var middleware = new HandlerMiddlewareBuilder(testResolver, testResolver) .UseBackoff(MessageBackoffStrategy) .UseDefaults <SimpleMessage>(handler.GetType()) .Build(); _middlewareMap.Add <OrderAccepted>(_queue.QueueName, middleware); MessageBackoffStrategy.GetBackoffDuration(_typedMessage, 1, _expectedException).Returns(_expectedBackoffTimeSpan); _sqsMessage.Attributes.Add(MessageSystemAttributeName.ApproximateReceiveCount, ExpectedReceiveCount.ToString(CultureInfo.InvariantCulture)); }
/// <summary> /// <para> /// Applies a set of default middlewares in order. Adding other middlewares before this will add them /// to the top of the stack, and are run first and last. /// Adding other middleware after this will add them to the bottom of the stack, just before the /// handler itself is invoked. /// </para> /// The middlewares this adds are, in order: /// <list type="bullet"> /// <item>MessageContextAccessorMiddleware</item> /// <item>BackoffMiddleware (only if an <see cref="IMessageBackoffStrategy"/> is available)</item> /// <item>ErrorHandlerMiddleware</item> /// <item>LoggingMiddleware</item> /// <item>StopwatchMiddleware</item> /// <item>SqsPostProcessorMiddleware</item> /// <item>HandlerInvocationMiddleware`1</item> /// </list> /// </summary> /// <param name="builder">The <see cref="HandlerMiddlewareBuilder"/> builder to add these defaults to.</param> /// <param name="handlerType">The type of the handler that will handle messages for this middleware pipeline. /// This is used when recording handler execution time with the StopwatchMiddleware.</param> /// <typeparam name="TMessage">The type of the message that this middleware pipeline handles.</typeparam> /// <returns>The current <see cref="HandlerMiddlewareBuilder"/>.</returns> public static HandlerMiddlewareBuilder UseDefaults <TMessage>( this HandlerMiddlewareBuilder builder, Type handlerType) where TMessage : Message { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (handlerType == null) { throw new ArgumentNullException(nameof(handlerType), "HandlerType is used here to"); } builder.UseMessageContextAccessor(); builder.UseErrorHandler(); builder.Use <LoggingMiddleware>(); builder.UseStopwatch(handlerType); builder.Use <SqsPostProcessorMiddleware>(); builder.UseHandler <TMessage>(); return(builder); }