public async void EnqueueAll(CancellationToken cancellationToken)
        {
            using (ProcessManagerDbContext context = _dbContextFactory.Invoke())
            {
Loop:

                List <Guid> withPendingCommand = await context
                                                 .PendingCommands
                                                 .Take(1)
                                                 .Select(c => c.ProcessManagerId)
                                                 .ToListAsync()
                                                 .ConfigureAwait(false);

                List <Guid> withPendingScheduledCommand = await context
                                                          .PendingScheduledCommands
                                                          .Take(1)
                                                          .Select(c => c.ProcessManagerId)
                                                          .ToListAsync(cancellationToken)
                                                          .ConfigureAwait(false);

                IEnumerable <Guid> processManagerIds = withPendingCommand.Union(withPendingScheduledCommand);
                IEnumerable <Task> flushTasks        = processManagerIds.Select(processManagerId => FlushCommands(processManagerId, cancellationToken));
                await Task.WhenAll(flushTasks).ConfigureAwait(false);

                if (withPendingCommand.Any() ||
                    withPendingScheduledCommand.Any())
                {
                    goto Loop;
                }
            }
        }
        public void model_has_PendingScheduledCommand_entity()
        {
            var         sut    = new ProcessManagerDbContext(_dbContextOptions);
            IEntityType actual = sut.Model.FindEntityType(typeof(PendingScheduledCommand));

            actual.Should().NotBeNull();
        }
        public void PendingScheduledCommand_entity_has_index_for_MessageId()
        {
            var         context  = new ProcessManagerDbContext(_dbContextOptions);
            IEntityType sut      = context.Model.FindEntityType(typeof(PendingScheduledCommand));
            IProperty   property = sut.FindProperty("MessageId");

            property.GetContainingIndexes().Should().ContainSingle(index => index.IsUnique);
        }
 private async Task RunFlushCommands(Guid processManagerId, CancellationToken cancellationToken)
 {
     using (ProcessManagerDbContext context = _dbContextFactory.Invoke())
     {
         await FlushPendingCommands(context, processManagerId, cancellationToken).ConfigureAwait(false);
         await FlushPendingScheduledCommands(context, processManagerId, cancellationToken).ConfigureAwait(false);
     }
 }
        public async Task FlushCommands_sends_all_scheduled_commands_associated_with_specified_process_manager_sequentially()
        {
            // Arrange
            var serializer          = new JsonMessageSerializer();
            var processManager      = new FakeProcessManager();
            var noiseProcessManager = new FakeProcessManager();

            var random  = new Random();
            var fixture = new Fixture();

            var scheduledEnvelopes = new List <ScheduledEnvelope>(
                from command in new[]
            {
                new FakeCommand {
                    Int32Value = random.Next(), StringValue = fixture.Create <string>()
                },
                new FakeCommand {
                    Int32Value = random.Next(), StringValue = fixture.Create <string>()
                },
                new FakeCommand {
                    Int32Value = random.Next(), StringValue = fixture.Create <string>()
                },
            }
                let envelope = new Envelope(Guid.NewGuid(), command, null, Guid.NewGuid(), null)
                               select new ScheduledEnvelope(envelope, DateTime.UtcNow.AddTicks(random.Next())));

            using (var db = new ProcessManagerDbContext(_dbContextOptions))
            {
                db.PendingScheduledCommands.AddRange(
                    from scheduledEnvelope in scheduledEnvelopes
                    select PendingScheduledCommand.FromScheduledEnvelope(processManager, scheduledEnvelope, serializer));

                db.PendingScheduledCommands.AddRange(
                    from scheduledEnvelope in new[]
                {
                    new ScheduledEnvelope(new Envelope(new object()), DateTime.UtcNow),
                    new ScheduledEnvelope(new Envelope(new object()), DateTime.UtcNow),
                    new ScheduledEnvelope(new Envelope(new object()), DateTime.UtcNow),
                }
                    select PendingScheduledCommand.FromScheduledEnvelope(noiseProcessManager, scheduledEnvelope, serializer));

                await db.SaveChangesAsync();
            }

            var scheduledMessageBus = new ScheduledMessageBus();

            var sut = new SqlCommandPublisher(
                () => new ProcessManagerDbContext(_dbContextOptions),
                serializer,
                Mock.Of <IMessageBus>(),
                scheduledMessageBus);

            // Act
            await sut.FlushCommands(processManager.Id, CancellationToken.None);

            // Assert
            scheduledMessageBus.Sent.Should().BeEquivalentTo(scheduledEnvelopes, opts => opts.WithStrictOrdering().RespectingRuntimeTypes());
        }
        public async Task given_message_bus_fails_FlushCommands_deletes_no_command()
        {
            // Arrange
            var serializer     = new JsonMessageSerializer();
            var processManager = new FakeProcessManager();
            var random         = new Random();
            var commands       = new List <PendingCommand>(
                from command in new[]
            {
                new FakeCommand {
                    Int32Value = random.Next(), StringValue = Guid.NewGuid().ToString()
                },
                new FakeCommand {
                    Int32Value = random.Next(), StringValue = Guid.NewGuid().ToString()
                },
                new FakeCommand {
                    Int32Value = random.Next(), StringValue = Guid.NewGuid().ToString()
                },
            }
                let envelope = new Envelope(command)
                               select PendingCommand.FromEnvelope(processManager, envelope, serializer));

            using (var db = new ProcessManagerDbContext(_dbContextOptions))
            {
                db.PendingCommands.AddRange(commands);
                await db.SaveChangesAsync();
            }

            IMessageBus messageBus = Mock.Of <IMessageBus>();
            var         exception  = new InvalidOperationException();

            Mock.Get(messageBus)
            .Setup(x => x.Send(It.IsAny <IEnumerable <Envelope> >(), It.IsAny <CancellationToken>()))
            .ThrowsAsync(exception);

            var sut = new SqlCommandPublisher(
                () => new ProcessManagerDbContext(_dbContextOptions),
                serializer,
                messageBus,
                Mock.Of <IScheduledMessageBus>());

            // Act
            Func <Task> action = () => sut.FlushCommands(processManager.Id, CancellationToken.None);

            // Assert
            action.Should().Throw <InvalidOperationException>().Which.Should().BeSameAs(exception);
            using (var db = new ProcessManagerDbContext(_dbContextOptions))
            {
                IQueryable <PendingCommand> query = from c in db.PendingCommands
                                                    where c.ProcessManagerId == processManager.Id
                                                    select c;
                List <PendingCommand> actual = await query.ToListAsync();

                actual.Should().BeEquivalentTo(commands, opts => opts.RespectingRuntimeTypes());
            }
        }
 private static async Task RemoveScheduledCommands(
     ProcessManagerDbContext dbContext,
     IEnumerable <PendingScheduledCommand> scheduledCommands,
     CancellationToken cancellationToken)
 {
     foreach (PendingScheduledCommand scheduledCommand in scheduledCommands)
     {
         await RemoveScheduledCommand(dbContext, scheduledCommand, cancellationToken).ConfigureAwait(false);
     }
 }
        private async Task FlushPendingScheduledCommands(
            ProcessManagerDbContext dbContext,
            Guid processManagerId,
            CancellationToken cancellationToken)
        {
            List <PendingScheduledCommand> scheduledCommands = await LoadScheduledCommands(dbContext, processManagerId, cancellationToken).ConfigureAwait(false);

            await SendScheduledCommands(scheduledCommands, cancellationToken).ConfigureAwait(false);
            await RemoveScheduledCommands(dbContext, scheduledCommands, cancellationToken).ConfigureAwait(false);
        }
 private static async Task RemoveCommands(
     ProcessManagerDbContext dbContext,
     List <PendingCommand> commands,
     CancellationToken cancellationToken)
 {
     foreach (PendingCommand command in commands)
     {
         await RemoveCommand(dbContext, command, cancellationToken).ConfigureAwait(false);
     }
 }
Beispiel #10
0
 public SqlProcessManagerDataContext(
     ProcessManagerDbContext dbContext,
     IMessageSerializer serializer,
     ICommandPublisher commandPublisher)
     : this(
         dbContext,
         serializer,
         commandPublisher,
         DefaultCommandPublisherExceptionHandler.Instance)
 {
 }
Beispiel #11
0
 public SqlProcessManagerDataContext(
     ProcessManagerDbContext dbContext,
     IMessageSerializer serializer,
     ICommandPublisher commandPublisher,
     ICommandPublisherExceptionHandler commandPublisherExceptionHandler)
 {
     _dbContext        = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
     _serializer       = serializer ?? throw new ArgumentNullException(nameof(serializer));
     _commandPublisher = commandPublisher ?? throw new ArgumentNullException(nameof(commandPublisher));
     _commandPublisherExceptionHandler = commandPublisherExceptionHandler ?? throw new ArgumentNullException(nameof(commandPublisherExceptionHandler));
 }
        public async Task FlushCommands_absorbs_exception_caused_by_that_some_pending_scheduled_command_already_deleted_since_loaded()
        {
            // Arrange
            var scheduledMessageBus = new CompletableScheduledMessageBus();
            var serializer          = new JsonMessageSerializer();
            var sut = new SqlCommandPublisher(
                () => new ProcessManagerDbContext(_dbContextOptions),
                serializer,
                Mock.Of <IMessageBus>(),
                scheduledMessageBus);

            var processManager = new FakeProcessManager();

            using (var db = new ProcessManagerDbContext(_dbContextOptions))
            {
                db.PendingScheduledCommands.AddRange(
                    from command in new[]
                {
                    new FakeCommand(),
                    new FakeCommand(),
                    new FakeCommand(),
                }
                    let envelope = new Envelope(command)
                                   let scheduledEnvelope = new ScheduledEnvelope(envelope, DateTime.UtcNow)
                                                           select PendingScheduledCommand.FromScheduledEnvelope(processManager, scheduledEnvelope, serializer));
                await db.SaveChangesAsync();
            }

            // Act
            Func <Task> action = async() =>
            {
                Task flushTask = sut.FlushCommands(processManager.Id, CancellationToken.None);
                using (var db = new ProcessManagerDbContext(_dbContextOptions))
                {
                    List <PendingScheduledCommand> pendingScheduledCommands = await db
                                                                              .PendingScheduledCommands
                                                                              .Where(c => c.ProcessManagerId == processManager.Id)
                                                                              .OrderByDescending(c => c.Id)
                                                                              .Take(1)
                                                                              .ToListAsync();

                    db.PendingScheduledCommands.RemoveRange(pendingScheduledCommands);
                    await db.SaveChangesAsync();
                }

                scheduledMessageBus.Complete();
                await flushTask;
            };

            // Assert
            action.Should().NotThrow();
        }
        private static Task <List <PendingCommand> > LoadCommands(
            ProcessManagerDbContext dbContext,
            Guid processManagerId,
            CancellationToken cancellationToken)
        {
            IQueryable <PendingCommand> query =
                from c in dbContext.PendingCommands
                where c.ProcessManagerId == processManagerId
                orderby c.Id
                select c;

            return(query.ToListAsync(cancellationToken));
        }
        private async Task FlushPendingCommands(
            ProcessManagerDbContext dbContext,
            Guid processManagerId,
            CancellationToken cancellationToken)
        {
            List <PendingCommand> commands = await LoadCommands(dbContext, processManagerId, cancellationToken).ConfigureAwait(false);

            if (commands.Any())
            {
                await SendCommands(commands, cancellationToken).ConfigureAwait(false);
                await RemoveCommands(dbContext, commands, cancellationToken).ConfigureAwait(false);
            }
        }
 private static async Task RemoveScheduledCommand(
     ProcessManagerDbContext dbContext,
     PendingScheduledCommand scheduledCommand,
     CancellationToken cancellationToken)
 {
     try
     {
         dbContext.PendingScheduledCommands.Remove(scheduledCommand);
         await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
     }
     catch (DbUpdateConcurrencyException)
     {
         dbContext.Entry(scheduledCommand).State = EntityState.Detached;
     }
 }
        public async Task FlushCommands_deletes_all_commands_associated_with_specified_process_manager()
        {
            // Arrange
            var serializer = new JsonMessageSerializer();

            var processManager      = new FakeProcessManager();
            var noiseProcessManager = new FakeProcessManager();

            const int noiseCommandCount = 3;

            using (var db = new ProcessManagerDbContext(_dbContextOptions))
            {
                var commands = new List <PendingCommand>(
                    from command in Enumerable.Repeat(new FakeCommand(), 3)
                    let envelope = new Envelope(command)
                                   select PendingCommand.FromEnvelope(processManager, envelope, serializer));

                commands.AddRange(
                    from command in Enumerable.Repeat(new FakeCommand(), noiseCommandCount)
                    let envelope = new Envelope(command)
                                   select PendingCommand.FromEnvelope(noiseProcessManager, envelope, serializer));

                var random = new Random();
                db.PendingCommands.AddRange(
                    from command in commands
                    orderby random.Next()
                    select command);

                await db.SaveChangesAsync();
            }

            var sut = new SqlCommandPublisher(
                () => new ProcessManagerDbContext(_dbContextOptions),
                serializer,
                Mock.Of <IMessageBus>(),
                Mock.Of <IScheduledMessageBus>());

            // Act
            await sut.FlushCommands(processManager.Id, CancellationToken.None);

            // Assert
            using (var db = new ProcessManagerDbContext(_dbContextOptions))
            {
                (await db.PendingCommands.AnyAsync(c => c.ProcessManagerId == processManager.Id)).Should().BeFalse();
                (await db.PendingCommands.CountAsync(c => c.ProcessManagerId == noiseProcessManager.Id)).Should().Be(noiseCommandCount);
            }
        }
        public async Task EnqueueAll_publishes_all_pending_scheduled_commands_asynchronously()
        {
            // Arrange
            var serializer = new JsonMessageSerializer();

            using (var db = new ProcessManagerDbContext(_dbContextOptions))
            {
                for (int i = 0; i < 3; i++)
                {
                    var processManager = new FakeProcessManager();
                    db.PendingScheduledCommands.AddRange(from command in Enumerable.Repeat(new FakeCommand(), 3)
                                                         let envelope = new Envelope(command)
                                                                        let scheduledEnvelope = new ScheduledEnvelope(envelope, DateTime.UtcNow)
                                                                                                select PendingScheduledCommand.FromScheduledEnvelope(processManager, scheduledEnvelope, serializer));
                }

                await db.SaveChangesAsync();
            }

            var sut = new SqlCommandPublisher(
                () => new ProcessManagerDbContext(_dbContextOptions),
                serializer,
                Mock.Of <IMessageBus>(),
                Mock.Of <IScheduledMessageBus>());

            // Act
            sut.EnqueueAll(CancellationToken.None);

            // Assert
            using (var db = new ProcessManagerDbContext(_dbContextOptions))
            {
                int maximumRetryCount = 5;
                var retryPolicy       = new RetryPolicy <bool>(
                    maximumRetryCount,
                    new DelegatingTransientFaultDetectionStrategy <bool>(any => any == true),
                    new ConstantRetryIntervalStrategy(TimeSpan.FromSeconds(1.0)));
                (await retryPolicy.Run(db.PendingScheduledCommands.AnyAsync, CancellationToken.None)).Should().BeFalse();
            }
        }
        public async Task given_scheduled_message_bus_fails_FlushCommands_deletes_no_scheduled_command()
        {
            // Arrange
            var serializer        = new JsonMessageSerializer();
            var processManager    = new FakeProcessManager();
            var random            = new Random();
            var scheduledCommands = new List <PendingScheduledCommand>(
                from command in new[]
            {
                new FakeCommand {
                    Int32Value = random.Next(), StringValue = Guid.NewGuid().ToString()
                },
                new FakeCommand {
                    Int32Value = random.Next(), StringValue = Guid.NewGuid().ToString()
                },
                new FakeCommand {
                    Int32Value = random.Next(), StringValue = Guid.NewGuid().ToString()
                },
            }
                let envelope = new Envelope(command)
                               let scheduledEnvelope = new ScheduledEnvelope(envelope, DateTime.UtcNow.AddTicks(random.Next()))
                                                       select PendingScheduledCommand.FromScheduledEnvelope(processManager, scheduledEnvelope, serializer));

            using (var db = new ProcessManagerDbContext(_dbContextOptions))
            {
                db.PendingScheduledCommands.AddRange(scheduledCommands);
                await db.SaveChangesAsync();
            }

            Guid poisonedMessageId = (from c in scheduledCommands
                                      orderby c.GetHashCode()
                                      select c.MessageId).First();

            IScheduledMessageBus scheduledMessageBus = Mock.Of <IScheduledMessageBus>();
            var exception = new InvalidOperationException();

            Mock.Get(scheduledMessageBus)
            .Setup(x => x.Send(It.Is <ScheduledEnvelope>(p => p.Envelope.MessageId == poisonedMessageId), CancellationToken.None))
            .ThrowsAsync(exception);

            var sut = new SqlCommandPublisher(
                () => new ProcessManagerDbContext(_dbContextOptions),
                serializer,
                Mock.Of <IMessageBus>(),
                scheduledMessageBus);

            // Act
            Func <Task> action = () => sut.FlushCommands(processManager.Id, CancellationToken.None);

            // Assert
            action.Should().Throw <InvalidOperationException>().Which.Should().BeSameAs(exception);
            using (var db = new ProcessManagerDbContext(_dbContextOptions))
            {
                IQueryable <PendingScheduledCommand> query = from c in db.PendingScheduledCommands
                                                             where c.ProcessManagerId == processManager.Id
                                                             select c;
                List <PendingScheduledCommand> actual = await query.ToListAsync();

                actual.Should().BeEquivalentTo(scheduledCommands, opts => opts.RespectingRuntimeTypes());
            }
        }