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); } }
public SqlProcessManagerDataContext( ProcessManagerDbContext dbContext, IMessageSerializer serializer, ICommandPublisher commandPublisher) : this( dbContext, serializer, commandPublisher, DefaultCommandPublisherExceptionHandler.Instance) { }
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()); } }