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();
        }
Ejemplo n.º 4
0
    /// <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)));
        }
Ejemplo n.º 6
0
    /// <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)));
    }
Ejemplo n.º 7
0
    /// <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);
        }
Ejemplo n.º 9
0
    /// <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)));
    }
Ejemplo n.º 10
0
        /// <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)));
        }
Ejemplo n.º 11
0
            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);
            }
Ejemplo n.º 12
0
    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;
    }
Ejemplo n.º 13
0
            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));
            }
Ejemplo n.º 14
0
    /// <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);
    }