public async Task A_command_is_not_marked_as_applied_if_no_handler_is_registered()
        {
            // arrange
            var order = CommandSchedulingTests.CreateOrder();

            order.Apply(
                new ChargeCreditCardOn
            {
                Amount     = 10,
                ChargeDate = Clock.Now().AddDays(10)
            });
            await orderRepository.Save(order);

            // act
            var schedulerWithNoHandlers = new SqlCommandScheduler(
                new Configuration().UseSqlEventStore());
            await schedulerWithNoHandlers.AdvanceClock(clockName, @by : TimeSpan.FromDays(20));

            // assert
            using (var db = new CommandSchedulerDbContext())
            {
                db.ScheduledCommands.Single(c => c.AggregateId == order.Id)
                .AppliedTime
                .Should()
                .BeNull();
            }
        }
        public override async Task Specific_scheduled_commands_can_be_triggered_directly_by_target_id()
        {
            // arrange
            var target = new CommandTarget(Any.CamelCaseName());
            await store.Put(target);

            // act
            await scheduler.Schedule(target.Id,
                                     new TestCommand(),
                                     Clock.Now().AddDays(2));

            using (var db = new CommandSchedulerDbContext())
            {
                var aggregateId = target.Id.ToGuidV3();
                var command     = db.ScheduledCommands
                                  .Single(c => c.AggregateId == aggregateId);
                await Configuration.Current
                .SchedulerClockTrigger()
                .Trigger(command, new SchedulerAdvancedResult(), db);
            }

            //assert
            target = await store.Get(target.Id);

            target.CommandsEnacted.Should().HaveCount(1);
        }
 private void InitializeCommandScheduler()
 {
     using (var context = new CommandSchedulerDbContext(CommandSchedulerConnectionString))
     {
         new EventStoreDatabaseInitializer <CommandSchedulerDbContext>().InitializeDatabase(context);
     }
 }
        public override async Task When_an_immediately_scheduled_command_depends_on_a_precondition_that_has_not_been_met_yet_then_there_is_not_initially_an_attempt_recorded()
        {
            // arrange
            var targetId     = Any.CamelCaseName();
            var precondition = new EventHasBeenRecordedPrecondition(
                Guid.NewGuid().ToString().ToETag(),
                Guid.NewGuid());

            // act
            await scheduler.Schedule(targetId,
                                     new CreateCommandTarget(targetId),
                                     deliveryDependsOn : precondition);

            // assert
            using (var db = new CommandSchedulerDbContext())
            {
                var aggregateId = targetId.ToGuidV3();
                var command     = db.ScheduledCommands.Single(c => c.AggregateId == aggregateId);

                command.AppliedTime
                .Should()
                .NotHaveValue();

                command.Attempts
                .Should()
                .Be(0);
            }
        }
示例#5
0
        /// <summary>
        /// Allows awaiting delivery of all commands that are currently due on the command scheduler.
        /// </summary>
        /// <param name="scheduler">The command scheduler.</param>
        /// <param name="clockName">The name of the clock on which the commands are scheduled.</param>
        /// <returns></returns>
        public static async Task Done(
            this ISchedulerClockTrigger scheduler,
            string clockName = null)
        {
            clockName = clockName ?? SqlCommandScheduler.DefaultClockName;

            for (var i = 0; i < 10; i++)
            {
                using (var db = new CommandSchedulerDbContext())
                {
                    var due = db.ScheduledCommands
                              .Due()
                              .Where(c => c.Clock.Name == clockName);

                    if (!await due.AnyAsync())
                    {
                        return;
                    }

                    var commands = await due.ToArrayAsync();

                    foreach (var scheduledCommand in commands)
                    {
                        await scheduler.Trigger(
                            scheduledCommand,
                            new SchedulerAdvancedResult(),
                            db);
                    }

                    await Task.Delay(400);
                }
            }
        }
 public async Task Command_scheduler_database_contains_a_default_clock()
 {
     using (var db = new CommandSchedulerDbContext(CommandSchedulerConnectionString))
     {
         db.Clocks.Should().ContainSingle(c => c.Name == "default");
     }
 }
        public override async Task When_triggering_specific_commands_then_the_result_can_be_used_to_evaluate_failures()
        {
            // arrange
            var target = new CommandTarget(Any.CamelCaseName());
            await store.Put(target);

            var schedulerAdvancedResult = new SchedulerAdvancedResult();

            // act
            await scheduler.Schedule(target.Id,
                                     new TestCommand(isValid : false),
                                     Clock.Now().AddDays(2));

            using (var db = new CommandSchedulerDbContext())
            {
                var aggregateId = target.Id.ToGuidV3();
                var command     = db.ScheduledCommands
                                  .Single(c => c.AggregateId == aggregateId);

                await Configuration.Current
                .SchedulerClockTrigger()
                .Trigger(command, schedulerAdvancedResult, db);
            }

            //assert
            schedulerAdvancedResult
            .FailedCommands
            .Should()
            .HaveCount(1);
        }
 /// <summary>
 /// Deserializes a scheduled command from SQL storage and delivers it to the target.
 /// </summary>
 /// <param name="configuration">The configuration.</param>
 /// <param name="serializedCommand">The serialized command.</param>
 /// <param name="db">The command scheduler database context.</param>
 public static async Task DeserializeAndDeliver(
     this Configuration configuration,
     ScheduledCommand serializedCommand,
     CommandSchedulerDbContext db) =>
         await DeserializeAndDeliver(
             configuration.Container.Resolve<CommandSchedulerResolver>(),
             serializedCommand,
             db);
 /// <summary>
 /// Deserializes a scheduled command from SQL storage and delivers it to the target.
 /// </summary>
 /// <param name="configuration">The configuration.</param>
 /// <param name="serializedCommand">The serialized command.</param>
 /// <param name="db">The command scheduler database context.</param>
 public static async Task DeserializeAndDeliver(
     this Configuration configuration,
     ScheduledCommand serializedCommand,
     CommandSchedulerDbContext db) =>
 await DeserializeAndDeliver(
     configuration.Container.Resolve <CommandDelivererResolver>(),
     serializedCommand,
     db);
示例#10
0
        public EventStoreDbTest()
        {
            Logging.Configure();

            SetConnectionStrings();

            Command <Order> .AuthorizeDefault           = (order, command) => true;
            Command <CustomerAccount> .AuthorizeDefault = (order, command) => true;

            lock (lockObj)
            {
                if (databasesInitialized)
                {
                    return;
                }

                // TODO: (EventStoreDbTest) figure out a db cleanup story
//#if !DEBUG
//            new EventStoreDbContext().Database.Delete();
//            new OtherEventStoreDbContext().Database.Delete();
//            new ReadModelDbContext().Database.Delete();
//            new ReadModels1DbContext().Database.Delete();
//            new ReadModels2DbContext().Database.Delete();
//            new CommandSchedulerDbContext().Database.Delete();
//#endif

                using (var eventStore = new EventStoreDbContext())
                {
                    new EventStoreDatabaseInitializer <EventStoreDbContext>().InitializeDatabase(eventStore);
                }
                using (var db = new CommandSchedulerDbContext())
                {
                    new CommandSchedulerDatabaseInitializer().InitializeDatabase(db);
                }
                using (var eventStore = new OtherEventStoreDbContext())
                {
                    new EventStoreDatabaseInitializer <OtherEventStoreDbContext>().InitializeDatabase(eventStore);
                }
                using (var db = new ReadModelDbContext())
                {
                    new ReadModelDatabaseInitializer <ReadModelDbContext>().InitializeDatabase(db);
                }
                using (var db = new ReadModels1DbContext())
                {
                    new ReadModelDatabaseInitializer <ReadModels1DbContext>().InitializeDatabase(db);
                }
                using (var db = new ReadModels2DbContext())
                {
                    new ReadModelDatabaseInitializer <ReadModels2DbContext>().InitializeDatabase(db);
                }

                databasesInitialized = true;
            }
        }
示例#11
0
        public static async Task Done(
            this ISchedulerClockTrigger scheduler,
            string clockName = null)
        {
            clockName = clockName ?? Sql.CommandScheduler.Clock.DefaultClockName;

            using (var db = new CommandSchedulerDbContext())
            {
                var now = Clock.Latest(Clock.Current, db.Clocks.Single(c => c.Name == clockName)).Now();
                await scheduler.AdvanceClock(clockName, now);
            }
        }
        internal static async Task DeserializeAndDeliver(
            CommandSchedulerResolver schedulerResolver,
            ScheduledCommand serializedCommand,
            CommandSchedulerDbContext db)
        {
            dynamic scheduler = schedulerResolver.ResolveSchedulerForAggregateTypeNamed(serializedCommand.AggregateType);

            await Storage.DeserializeAndDeliverScheduledCommand(
                serializedCommand,
                scheduler);

            serializedCommand.Attempts++;

            await db.SaveChangesAsync();
        }
示例#13
0
        private static async Task DeserializeAndDeliver(
            CommandSchedulerResolver schedulerResolver,
            ScheduledCommand serializedCommand,
            CommandSchedulerDbContext db)
        {
            dynamic scheduler = schedulerResolver.ResolveSchedulerForAggregateTypeNamed(serializedCommand.AggregateType);

            await Storage.DeserializeAndDeliverScheduledCommand(
                serializedCommand,
                scheduler);

            serializedCommand.Attempts++;

            await db.SaveChangesAsync();
        }
        private MigrationResult Migrate(CommandSchedulerDbContext ctx)
        {
            var sql = @"
delete from Scheduler.ScheduledCommand
where AppliedTime is not null and AppliedTime < {0}";

            var numberOfRecords = ctx.Database.ExecuteSqlCommand(
                sql,
                cutoffDate);

            return new MigrationResult
            {
                Log = $"Deleted {numberOfRecords} records\n{string.Format(sql, cutoffDate)}",
                MigrationWasApplied = true
            };
        }
        public override async Task When_a_command_is_scheduled_but_an_exception_is_thrown_in_a_handler_then_an_error_is_recorded()
        {
            // arrange
            var target = new CommandTarget(Any.CamelCaseName());
            await store.Put(target);

            // act
            await scheduler.Schedule(target.Id,
                                     new TestCommand(isValid : false));

            //assert
            using (var db = new CommandSchedulerDbContext())
            {
                var aggregateId = target.Id.ToGuidV3();
                var error       = db.Errors.Single(e => e.ScheduledCommand.AggregateId == aggregateId);

                error.Error.Should().Contain("CommandValidationException");
            }
        }
        public static void ClassInitialize(TestContext context)
        {
            CommandSchedulerDbContext.NameOrConnectionString = @"Data Source=(localdb)\v11.0; Integrated Security=True; MultipleActiveResultSets=False; Initial Catalog=ItsCqrsTestsCommandScheduler";

            Settings.Sources        = new ISettingsSource[] { new ConfigDirectorySettings(@"c:\dev\.config") }.Concat(Settings.Sources);
            settings                = Settings.Get <ServiceBusSettings>();
            settings.ConfigureQueue = queue => queue.AutoDeleteOnIdle = TimeSpan.FromHours(24);

#if !DEBUG
            new CommandSchedulerDbContext().Database.Delete();
#endif

            using (var readModels = new CommandSchedulerDbContext())
            {
                new CommandSchedulerDatabaseInitializer().InitializeDatabase(readModels);
            }

            Console.WriteLine(Environment.MachineName);
            Console.WriteLine(new DirectoryInfo(@"c:\dev\.config").GetFiles().Select(f => f.FullName).ToLogString());
            Settings.Sources = new ISettingsSource[] { new ConfigDirectorySettings(@"c:\dev\.config") }.Concat(Settings.Sources);
        }
        public override async Task When_a_clock_is_advanced_and_a_command_fails_to_be_deserialized_then_other_commands_are_still_applied()
        {
            var commandScheduler = Configuration.Current.CommandScheduler <CommandTarget>();

            var failedTargetId     = Any.CamelCaseName();
            var successfulTargetId = Any.CamelCaseName();

            await commandScheduler.Schedule(failedTargetId,
                                            new CreateCommandTarget(failedTargetId),
                                            Clock.Now().AddHours(1));

            await commandScheduler.Schedule(successfulTargetId,
                                            new CreateCommandTarget(successfulTargetId),
                                            Clock.Now().AddHours(1.5));

            using (var db = new CommandSchedulerDbContext())
            {
                var aggregateId = failedTargetId.ToGuidV3();

                var command     = db.ScheduledCommands.Single(c => c.AggregateId == aggregateId);
                var commandBody = command.SerializedCommand.FromJsonTo <dynamic>();
                commandBody.Command.CommandName = "not a command name";
                command.SerializedCommand       = commandBody.ToString();
                db.SaveChanges();
            }

            // act
            Action advanceClock = () => Configuration.Current
                                  .SchedulerClockTrigger()
                                  .AdvanceClock(clockName: clockName,
                                                @by: TimeSpan.FromHours(2)).Wait();

            // assert
            advanceClock.ShouldNotThrow();

            var successfulAggregate = await store.Get(successfulTargetId);

            successfulAggregate.Should().NotBeNull();
        }
        public async Task When_a_scheduled_command_depends_on_an_event_that_never_arrives_it_is_eventually_abandoned()
        {
            VirtualClock.Start();
            var container = Configuration.Current.Container;
            var commandScheduler = container.Resolve<ICommandScheduler<Order>>();

            var orderId = Any.Guid();
            await orderRepository.Save(new Order(new CreateOrder(Any.FullName())
            {
                AggregateId = orderId
            }));

            var prerequisiteEvent = new CustomerAccount.Created
            {
                AggregateId = Any.Guid(),
                ETag = Any.Guid().ToString()
            };

            await commandScheduler.Schedule(orderId,
                                            new AddItem
                                            {
                                                ProductName = Any.Paragraph(3),
                                                Price = 10m
                                            },
                                            deliveryDependsOn: prerequisiteEvent);

            for (var i = 0; i < 7; i++)
            {
                VirtualClock.Current.AdvanceBy(TimeSpan.FromMinutes(90));
                Console.WriteLine(
                    (await clockTrigger.Trigger(commands => commands.Due().Where(c => c.AggregateId == orderId))).ToLogString());
            }

            using (var db = new CommandSchedulerDbContext())
            {
                var command = db.ScheduledCommands.Single(c => c.AggregateId == orderId);

                command.AppliedTime
                       .Should()
                       .BeNull();

                command.Attempts
                       .Should()
                       .BeGreaterOrEqualTo(5);

                command.FinalAttemptTime
                       .Should()
                       .NotBeNull();
            }
        }
        public override async Task When_a_clock_is_set_on_a_command_then_it_takes_precedence_over_GetClockName()
        {
            // arrange
            var clockName = Any.CamelCaseName();
            var targetId = Any.CamelCaseName();
            var command = new CreateCommandTarget(targetId);
            var clock = new CommandScheduler.Clock
            {
                Name = clockName,
                UtcNow = DateTimeOffset.Parse("2016-03-01 02:00:00 AM")
            };

            using (var commandScheduler = new CommandSchedulerDbContext())
            {
                commandScheduler.Clocks.Add(clock);
                commandScheduler.SaveChanges();
            }

            var scheduledCommand = new ScheduledCommand<CommandTarget>(
                targetId: targetId,
                command: command,
                dueTime: DateTimeOffset.Parse("2016-03-20 09:00:00 AM"))
            {
                Clock = clock
            };

            // act
            await scheduler.Schedule(scheduledCommand);

            await Configuration.Current.SchedulerClockTrigger()
                .AdvanceClock(clockName, by: 30.Days());

            //assert 
            var target = await store.Get(targetId);

            target.Should().NotBeNull();
        }
        public async Task When_a_scheduler_assigned_negative_SequenceNumber_collides_then_it_retries_scheduling_with_a_new_SequenceNumber()
        {
            // arrange
            var container = Configuration.Current.Container;
            var commandScheduler = container.Resolve<ICommandScheduler<Order>>();

            var initialSequenceNumber = -Any.PositiveInt();

            var orderId = Any.Guid();

            await commandScheduler.Schedule(new CommandScheduled<Order>
            {
                AggregateId = orderId,
                Command = new Cancel(),
                SequenceNumber = initialSequenceNumber,
                DueTime = Clock.Now().AddYears(1)
            });

            var secondCommand = new CommandScheduled<Order>
            {
                AggregateId = orderId,
                Command = new Cancel(),
                SequenceNumber = initialSequenceNumber,
                DueTime = Clock.Now().AddYears(1)
            };

            await commandScheduler.Schedule(secondCommand);

            using (var db = new CommandSchedulerDbContext())
            {
                db.ScheduledCommands
                  .Count(c => c.AggregateId == orderId)
                  .Should()
                  .Be(2);
            }
        }
        public async Task When_a_command_is_scheduled_but_the_event_that_triggered_it_was_not_successfully_written_then_the_command_is_not_applied()
        {
            VirtualClock.Start();

            // create a customer account
            var customer = new CustomerAccount(Any.Guid())
                .Apply(new ChangeEmailAddress(Any.Email()));
            await accountRepository.Save(customer);

            var order = new Order(new CreateOrder(Any.FullName())
            {
                CustomerId = customer.Id
            });

            // act
            // cancel the order but don't save it
            order.Apply(new Cancel());

            // assert
            // verify that the customer did not receive the scheduled NotifyOrderCanceled command
            customer = await accountRepository.GetLatest(customer.Id);
            customer.Events().Last().Should().BeOfType<CustomerAccount.EmailAddressChanged>();

            using (var db = new CommandSchedulerDbContext())
            {
                db.ScheduledCommands
                  .Where(c => c.AggregateId == customer.Id)
                  .Should()
                  .ContainSingle(c => c.AppliedTime == null &&
                                      c.DueTime == null);
            }
        }
        public async Task When_a_command_is_delivered_a_second_time_with_the_same_ETag_it_is_not_retried_afterward()
        {
            // arrange
            var order = new Order(new CreateOrder(Any.FullName())
            {
                CustomerId = Any.Guid()
            });
            await orderRepository.Save(order);

            var command = new AddItem
            {
                ProductName = Any.Word(),
                Price = 10m,
                ETag = Any.Guid().ToString()
            };

            var commandScheduler = Configuration.Current.Container.Resolve<ICommandScheduler<Order>>();

            // act
            await commandScheduler.Schedule(order.Id, command, Clock.Now().AddDays(1));
            Thread.Sleep(1); // the sequence number is set from the current tick count, which every now and then produces a duplicate here 
            await commandScheduler.Schedule(order.Id, command, Clock.Now().AddDays(1));
            await clockTrigger.Trigger(cmd => cmd.Where(c => c.AggregateId == order.Id));

            // assert
            order = await orderRepository.GetLatest(order.Id);

            order.Balance.Should().Be(10);

            using (var db = new CommandSchedulerDbContext())
            {
                db.ScheduledCommands
                  .Where(c => c.AggregateId == order.Id)
                  .Should()
                  .OnlyContain(c => c.AppliedTime != null);

                db.Errors
                  .Where(c => c.ScheduledCommand.AggregateId == order.Id)
                  .Should()
                  .BeEmpty();
            }
        }
        public async Task When_a_scheduled_command_fails_then_the_error_is_recorded()
        {
            // arrange
            var order = CommandSchedulingTests.CreateOrder();
            var innerRepository = new SqlEventSourcedRepository<Order>();
            var saveCount = 0;
            Configuration.Current
                         .Container
                         .Register<IEventSourcedRepository<Order>>(c => new FakeEventSourcedRepository<Order>(innerRepository)
                         {
                             OnSave = async o =>
                             {
                                 if (saveCount > 0)
                                 {
                                     throw new Exception("oops!");
                                 }
                                 await innerRepository.Save(o);
                                 saveCount++;
                             }
                         });

            // act
            order.Apply(new ShipOn(Clock.Now().AddDays(30)));
            await Configuration.Current.Repository<Order>().Save(order);
            await clockTrigger.AdvanceClock(clockName, TimeSpan.FromDays(31));

            //assert 
            using (var db = new CommandSchedulerDbContext())
            {
                var error = db.Errors.Single(c => c.ScheduledCommand.AggregateId == order.Id).Error;
                error.Should().Contain("oops!");
            }
        }
 protected int GetScheduledCommandNumberOfAttempts(Guid aggregateId)
 {
     using (var db = new CommandSchedulerDbContext())
     {
         var scheduledCommand = db.ScheduledCommands.SingleOrDefault(c => c.AggregateId == aggregateId);
         return scheduledCommand.IfNotNull()
                                .Then(c => c.Attempts)
                                .ElseDefault();
     }
 }
        public override async Task When_a_clock_is_set_on_a_command_then_it_takes_precedence_over_GetClockName()
        {
            // arrange
            var clockName = Any.CamelCaseName();
            var create = new CreateOrder(Any.FullName())
            {
                AggregateId = Any.Guid()
            };

            var clock = new CommandScheduler.Clock
            {
                Name = clockName,
                UtcNow = DateTimeOffset.Parse("2016-03-01 02:00:00 AM")
            };

            using (var commandScheduler = new CommandSchedulerDbContext())
            {
                commandScheduler.Clocks.Add(clock);
                commandScheduler.SaveChanges();
            }

            var scheduledCommand = new ScheduledCommand<Order>(
                aggregateId: create.AggregateId,
                command: create,
                dueTime: DateTimeOffset.Parse("2016-03-20 09:00:00 AM"))
            {
                Clock = clock
            };

            // act
            var configuration = Configuration.Current;
            await configuration.CommandScheduler<Order>().Schedule(scheduledCommand);

            await configuration.SchedulerClockTrigger()
                               .AdvanceClock(clockName,
                                             by: 30.Days());

            //assert 
            var target = await configuration.Repository<Order>().GetLatest(create.AggregateId);

            target.Should().NotBeNull();
        }
        public override async Task Specific_scheduled_commands_can_be_triggered_directly_by_target_id()
        {
            // arrange
            var target = new CommandTarget(Any.CamelCaseName());
            await store.Put(target);

            // act
            await scheduler.Schedule(target.Id,
                                     new TestCommand(),
                                     Clock.Now().AddDays(2));

            using (var db = new CommandSchedulerDbContext())
            {
                var aggregateId = target.Id.ToGuidV3();
                var command = db.ScheduledCommands
                                .Single(c => c.AggregateId == aggregateId);
                await Configuration.Current
                                   .SchedulerClockTrigger()
                                   .Trigger(command, new SchedulerAdvancedResult(), db);
            }

            //assert 
            target = await store.Get(target.Id);

            target.CommandsEnacted.Should().HaveCount(1);
        }
        public override async Task When_triggering_specific_commands_then_the_result_can_be_used_to_evaluate_failures()
        {
            // arrange
            var target = new CommandTarget(Any.CamelCaseName());
            await store.Put(target);
            var schedulerAdvancedResult = new SchedulerAdvancedResult();

            // act
            await scheduler.Schedule(target.Id,
                                     new TestCommand(isValid: false),
                                     Clock.Now().AddDays(2));

            using (var db = new CommandSchedulerDbContext())
            {
                var aggregateId = target.Id.ToGuidV3();
                var command = db.ScheduledCommands
                                .Single(c => c.AggregateId == aggregateId);

                await Configuration.Current
                                   .SchedulerClockTrigger()
                                   .Trigger(command, schedulerAdvancedResult, db);
            }

            //assert 
            schedulerAdvancedResult
                .FailedCommands
                .Should()
                .HaveCount(1);
        }
 public async Task Trigger(
     ScheduledCommand scheduled,
     SchedulerAdvancedResult result,
     CommandSchedulerDbContext db) =>
         await deliver(scheduled, result, db);
        public override async Task When_a_command_is_scheduled_but_an_exception_is_thrown_in_a_handler_then_an_error_is_recorded()
        {
            // arrange
            var target = new CommandTarget(Any.CamelCaseName());
            await store.Put(target);

            // act
            await scheduler.Schedule(target.Id,
                                     new TestCommand(isValid: false));

            //assert 
            using (var db = new CommandSchedulerDbContext())
            {
                var aggregateId = target.Id.ToGuidV3();
                var error = db.Errors.Single(e => e.ScheduledCommand.AggregateId == aggregateId);

                error.Error.Should().Contain("CommandValidationException");
            }
        }
        public override async Task When_an_immediately_scheduled_command_depends_on_a_precondition_that_has_not_been_met_yet_then_there_is_not_initially_an_attempt_recorded()
        {
            // arrange
            var targetId = Any.CamelCaseName();
            var precondition = new EventHasBeenRecordedPrecondition(
                Guid.NewGuid().ToString().ToETag(),
                Guid.NewGuid());

            // act
            await scheduler.Schedule(targetId,
                                     new CreateCommandTarget(targetId),
                                     deliveryDependsOn: precondition);
        
            // assert
            using (var db = new CommandSchedulerDbContext())
            {
                var aggregateId = targetId.ToGuidV3();
                var command = db.ScheduledCommands.Single(c => c.AggregateId == aggregateId);

                command.AppliedTime
                       .Should()
                       .NotHaveValue();

                command.Attempts
                       .Should()
                       .Be(0);
            }
        }
        public async Task UseSqlCommandScheduling_can_include_using_a_catchup_for_durability()
        {
            var configuration = new Configuration()
                .UseSqlEventStore()
                .UseSqlCommandScheduling(catchup => catchup.StartAtEventId = eventStoreDbTest.HighestEventId);
            configuration.SqlCommandScheduler().GetClockName = e => clockName;
            disposables.Add(configuration);

            var aggregateIds = Enumerable.Range(1, 5).Select(_ => Any.Guid()).ToArray();

            Events.Write(5, i => new CommandScheduled<Order>
            {
                Command = new CreateOrder(Any.FullName()),
                AggregateId = aggregateIds[i - 1],
                SequenceNumber = -DateTimeOffset.UtcNow.Ticks
            });

            await configuration.Container
                               .Resolve<ReadModelCatchup<CommandSchedulerDbContext>>()
                               .SingleBatchAsync();

            using (var db = new CommandSchedulerDbContext())
            {
                var scheduledAggregateIds = db.ScheduledCommands
                                              .Where(c => aggregateIds.Any(id => id == c.AggregateId))
                                              .Select(c => c.AggregateId)
                                              .ToArray();

                scheduledAggregateIds.Should()
                                     .BeEquivalentTo(aggregateIds);
            }
        }
        public async Task When_a_command_is_non_durable_then_immediate_scheduling_does_not_result_in_a_command_scheduler_db_entry()
        {
            var commandScheduler = Configuration.Current.Container.Resolve<ICommandScheduler<CustomerAccount>>();
            var reserverationService = new Mock<IReservationService>();
            reserverationService.Setup(m => m.Reserve(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TimeSpan?>()))
                                .Returns(() => Task.FromResult(true));
            Configuration.Current.ReservationService = reserverationService.Object;

            var aggregateId = Any.Guid();
            await accountRepository.Save(new CustomerAccount(aggregateId).Apply(new RequestNoSpam()));
            await commandScheduler.Schedule(aggregateId, new RequestUserName
            {
                UserName = Any.Email()
            });

            using (var db = new CommandSchedulerDbContext())
            {
                var scheduledAggregateIds = db.ScheduledCommands
                                              .Where(c => aggregateId == c.AggregateId)
                                              .ToArray();

                scheduledAggregateIds.Should()
                                     .BeEmpty();
            }
        }
        public override async Task When_a_clock_is_advanced_and_a_command_fails_to_be_deserialized_then_other_commands_are_still_applied()
        {
            var commandScheduler = Configuration.Current.CommandScheduler<Order>();
        
            var failedAggregateId = Any.Guid();
            var successfulAggregateId = Any.Guid();

            await commandScheduler.Schedule(failedAggregateId,
                                            new CreateOrder(Any.FullName()),
                                            Clock.Now().AddHours(1)); 
            await commandScheduler.Schedule(successfulAggregateId,
                                            new CreateOrder(Any.FullName())
                                            {
                                                AggregateId = successfulAggregateId
                                            },
                                            Clock.Now().AddHours(1.5));

            using (var db = new CommandSchedulerDbContext())
            {
                var command = db.ScheduledCommands.Single(c => c.AggregateId == failedAggregateId);
                var commandBody = command.SerializedCommand.FromJsonTo<dynamic>();
                commandBody.Command.CustomerId = "not a guid";
                command.SerializedCommand = commandBody.ToString();
                db.SaveChanges();
            }

            // act
            Action advanceClock = () => clockTrigger.AdvanceClock(clockName: clockName,
                                                                  @by: TimeSpan.FromHours(2)).Wait();

            // assert
            advanceClock.ShouldNotThrow();

            var repository = Configuration.Current.Repository<Order>();
            var successfulAggregate = await repository.GetLatest(successfulAggregateId);
            successfulAggregate.Should().NotBeNull();
        }
        public async Task When_a_scheduled_command_fails_due_to_a_concurrency_exception_then_it_is_not_marked_as_applied()
        {
            // arrange 
            var order = CommandSchedulingTests.CreateOrder();
            var ship = new Ship();

            order.Apply(ship);

            order.Apply(new ChargeCreditCardOn
            {
                Amount = 10,
                ChargeDate = Clock.Now().AddDays(10)
            });

            await orderRepository.Save(order);

            TriggerConcurrencyExceptionOnOrderCommands(order.Id);

            // act
            await clockTrigger.AdvanceClock(clockName, @by: TimeSpan.FromDays(20));

            // assert
            using (var db = new CommandSchedulerDbContext())
            {
                // make sure we actually triggered a concurrency exception
                db.Errors
                  .Where(e => e.ScheduledCommand.AggregateId == order.Id)
                  .Should()
                  .Contain(e => e.Error.Contains("ConcurrencyException"));

                var scheduledCommand = db.ScheduledCommands.Single(c => c.AggregateId == order.Id);

                scheduledCommand.AppliedTime.Should().BeNull();
                scheduledCommand.Attempts.Should().Be(1);
            }
        }
 public async Task Trigger(ScheduledCommand scheduled, SchedulerAdvancedResult result, CommandSchedulerDbContext db)
 {
     await ClockTrigger.Trigger(scheduled, result, db);
 }
        public async Task Specific_scheduled_commands_can_be_triggered_directly_by_aggregate_id()
        {
            // arrange
            var shipmentId = Any.AlphanumericString(8, 8);
            var order = CommandSchedulingTests.CreateOrder();

            order.Apply(new ShipOn(shipDate: Clock.Now().AddMonths(1).Date)
            {
                ShipmentId = shipmentId
            });
            await orderRepository.Save(order);

            // act
            await clockTrigger.Trigger(commands => commands.Where(cmd => cmd.AggregateId == order.Id));

            //assert 
            order = await orderRepository.GetLatest(order.Id);
            var lastEvent = order.Events().Last();
            lastEvent.Should().BeOfType<Order.Shipped>();
            order.ShipmentId
                 .Should()
                 .Be(shipmentId);

            using (var db = new CommandSchedulerDbContext())
            {
                db.ScheduledCommands
                  .Where(c => c.AggregateId == order.Id)
                  .Should()
                  .OnlyContain(c => c.AppliedTime != null);
            }
        }
        public async Task When_a_non_scheduler_assigned_negative_SequenceNumber_collides_then_it_is_ignores()
        {
            // arrange
            var container = Configuration.Current.Container;
            var commandScheduler = container.Resolve<ICommandScheduler<Order>>();

            var db = new CommandSchedulerDbContext();

            disposables.Add(db);

            var initialSequenceNumber = Any.PositiveInt();

            var orderId = Any.Guid();

            await commandScheduler.Schedule(new CommandScheduled<Order>
            {
                AggregateId = orderId,
                Command = new Cancel(),
                SequenceNumber = initialSequenceNumber,
                DueTime = Clock.Now().AddYears(1)
            });

            var secondCommand = new CommandScheduled<Order>
            {
                AggregateId = orderId,
                Command = new Cancel(),
                SequenceNumber = initialSequenceNumber,
                DueTime = Clock.Now().AddYears(1)
            };

            Action again = () => commandScheduler.Schedule(secondCommand).Wait();

            again.ShouldNotThrow<DbUpdateException>();
        }
        public async Task When_a_command_is_scheduled_but_an_exception_is_thrown_in_a_handler_then_an_error_is_recorded()
        {
            Configuration.Current.UseDependency(_ => new CustomerAccount.OrderEmailConfirmer
            {
                SendOrderConfirmationEmail = x => { throw new Exception("drat!"); }
            });

            // create a customer account
            var customer = new CustomerAccount(Any.Guid())
                .Apply(new ChangeEmailAddress(Any.Email()));
            await accountRepository.Save(customer);

            var order = new Order(new CreateOrder(Any.FullName())
            {
                CustomerId = customer.Id
            });
            await orderRepository.Save(order);

            // act
            order.Apply(new Cancel());
            await orderRepository.Save(order);

            await clockTrigger.AdvanceClock(clockName, TimeSpan.FromMinutes(1.2));

            using (var db = new CommandSchedulerDbContext())
            {
                db.Errors
                  .Where(e => e.ScheduledCommand.AggregateId == customer.Id)
                  .Should()
                  .Contain(e => e.Error.Contains("drat!"));
            }
        }
        public async Task When_a_constructor_commands_fails_with_a_ConcurrencyException_it_is_not_retried()
        {
            var commandScheduler = Configuration.Current.Container.Resolve<ICommandScheduler<Order>>();

            var orderId = Any.Guid();
            var customerName = Any.FullName();

            await commandScheduler.Schedule(orderId, new CreateOrder(customerName)
            {
                AggregateId = orderId,
                ETag = Any.Guid().ToString()
            });

            await commandScheduler.Schedule(orderId, new CreateOrder(customerName)
            {
                AggregateId = orderId
            });

            using (var db = new CommandSchedulerDbContext())
            {
                var commands = db.ScheduledCommands
                                 .Where(c => c.AggregateId == orderId)
                                 .OrderBy(c => c.CreatedTime)
                                 .ToArray();

                commands.Count().Should().Be(2);
                commands.Last().FinalAttemptTime.Should().NotBeNull();
            }
        }
        public async Task When_a_command_is_scheduled_but_the_aggregate_it_applies_to_is_not_found_then_the_command_is_retried()
        {
            // create and cancel an order for a nonexistent customer account 
            var customerId = Any.Guid();
            Console.WriteLine(new { customerId });
            var order = new Order(new CreateOrder(Any.FullName())
            {
                CustomerId = customerId
            });

            order.Apply(new Cancel());
            await orderRepository.Save(order);

            using (var db = new CommandSchedulerDbContext())
            {
                db.ScheduledCommands
                  .Where(c => c.AggregateId == customerId)
                  .Should()
                  .ContainSingle(c => c.AppliedTime == null);
            }

            // act
            // now save the customer and advance the clock
            await accountRepository.Save(new CustomerAccount(customerId).Apply(new ChangeEmailAddress(Any.Email())));

            await clockTrigger.AdvanceClock(clockName, TimeSpan.FromMinutes(2));

            using (var db = new CommandSchedulerDbContext())
            {
                db.ScheduledCommands
                  .Where(c => c.AggregateId == customerId)
                  .Should()
                  .ContainSingle(c => c.AppliedTime != null);

                var customer = await accountRepository.GetLatest(customerId);
                customer.Events()
                        .Last()
                        .Should().BeOfType<CustomerAccount.OrderCancelationConfirmationEmailSent>();
            }
        }
        public async Task When_an_immediately_scheduled_command_depends_on_an_event_that_has_not_been_saved_yet_then_there_is_not_initially_a_concurrency_exception()
        {
            var container = Configuration.Current.Container;
            var commandScheduler = container.Resolve<ICommandScheduler<Order>>();

            var orderId = Any.Guid();
            var customerName = Any.FullName();

            var prerequisiteEvent = new CustomerAccount.Created
            {
                AggregateId = Any.Guid(),
                ETag = Any.Guid().ToString()
            };
           
             var scheduledCommand =   await commandScheduler.Schedule(orderId,
                                                new CreateOrder(customerName)
                                                {
                                                    AggregateId = orderId
                                                },
                                                deliveryDependsOn: prerequisiteEvent);

            var order = await orderRepository.GetLatest(orderId);

            order.Should().BeNull();

            scheduledCommand.Result.Should().BeOfType<CommandScheduled>();

            using (var db = new CommandSchedulerDbContext())
            {
                var command = db.ScheduledCommands.Single(c => c.AggregateId == orderId);

                command.AppliedTime
                       .Should()
                       .BeNull();

                command.Attempts
                       .Should()
                       .Be(0);
            }
        }
        public override async Task When_a_constructor_command_fails_with_a_ConcurrencyException_it_is_not_retried()
        {
            var commandScheduler = Configuration.Current.CommandScheduler<Order>();

            var orderId = Any.Guid();
            var customerName = Any.FullName();

            await commandScheduler.Schedule(orderId, new CreateOrder(customerName)
            {
                AggregateId = orderId
            });

            await commandScheduler.Schedule(orderId, new CreateOrder(customerName)
            {
                AggregateId = orderId
            });

            using (var db = new CommandSchedulerDbContext())
            {
                var commands = db.ScheduledCommands
                                 .Where(c => c.AggregateId == orderId)
                                 .OrderBy(c => c.CreatedTime)
                                 .ToArray();

                Console.WriteLine(commands.ToDiagnosticJson());
                commands.Length.Should().Be(2);
                commands
                    .Should()
                    .ContainSingle(c => c.FinalAttemptTime == null);
            }
        }
        public override async Task When_a_clock_is_advanced_and_a_command_fails_to_be_deserialized_then_other_commands_are_still_applied()
        {
              var commandScheduler = Configuration.Current.CommandScheduler<CommandTarget>();
        
            var failedTargetId = Any.CamelCaseName();
            var successfulTargetId = Any.CamelCaseName();

            await commandScheduler.Schedule(failedTargetId,
                                            new CreateCommandTarget(failedTargetId),
                                            Clock.Now().AddHours(1)); 
            await commandScheduler.Schedule(successfulTargetId,
                                            new CreateCommandTarget(successfulTargetId),
                                            Clock.Now().AddHours(1.5));

            using (var db = new CommandSchedulerDbContext())
            {
                var aggregateId = failedTargetId.ToGuidV3();

                var command = db.ScheduledCommands.Single(c => c.AggregateId == aggregateId);
                var commandBody = command.SerializedCommand.FromJsonTo<dynamic>();
                commandBody.Command.CommandName = "not a command name";
                command.SerializedCommand = commandBody.ToString();
                db.SaveChanges();
            }

            // act
            Action advanceClock = () => Configuration.Current
                                                     .SchedulerClockTrigger()
                                                     .AdvanceClock(clockName: clockName,
                                                                   @by: TimeSpan.FromHours(2)).Wait();

            // assert
            advanceClock.ShouldNotThrow();

            var successfulAggregate = await store.Get(successfulTargetId);
            successfulAggregate.Should().NotBeNull();
        }
示例#44
0
        public async Task Recursive_scheduling_is_supported_when_the_virtual_clock_is_advanced()
        {
            // arrange
            using (VirtualClock.Start(DateTimeOffset.Parse("2014-10-08 06:52:10 AM -07:00")))
            {
                var scenario = CreateScenarioBuilder()
                               .AddEvents(new CustomerAccount.EmailAddressChanged
                {
                    NewEmailAddress = Any.Email()
                })
                               .Prepare();

                // act
                var account = (await scenario.GetLatestAsync <CustomerAccount>())
                              .Apply(new RequestSpam())
                              .Apply(new SendMarketingEmail());
                await scenario.SaveAsync(account);

                VirtualClock.Current.AdvanceBy(TimeSpan.FromDays((7 * 4) + 2));

                await scenario.CommandSchedulerDone();

                account = await scenario.GetLatestAsync <CustomerAccount>();

                // assert
                account.Events()
                .OfType <CommandScheduled <CustomerAccount> >()
                .Select(e => e.Timestamp.Date)
                .Should()
                .BeEquivalentTo(new[]
                {
                    DateTime.Parse("2014-10-08"),
                    DateTime.Parse("2014-10-15"),
                    DateTime.Parse("2014-10-22"),
                    DateTime.Parse("2014-10-29"),
                    DateTime.Parse("2014-11-05")
                });
                account.Events()
                .OfType <CustomerAccount.MarketingEmailSent>()
                .Select(e => e.Timestamp.Date)
                .Should()
                .BeEquivalentTo(new[]
                {
                    DateTime.Parse("2014-10-08"),
                    DateTime.Parse("2014-10-15"),
                    DateTime.Parse("2014-10-22"),
                    DateTime.Parse("2014-10-29"),
                    DateTime.Parse("2014-11-05")
                });
                account.Events()
                .Last()
                .Should()
                .BeOfType <CommandScheduled <CustomerAccount> >();

                if (this is ScenarioBuilderWithSqlEventStoreTests)
                {
                    using (var db = new CommandSchedulerDbContext())
                    {
                        var scheduledCommands = db.ScheduledCommands
                                                .Where(c => c.AggregateId == account.Id)
                                                .ToArray();

                        // all but the last command (which didn't come due yet) should have been marked as applied
                        scheduledCommands
                        .Reverse()
                        .Skip(1)
                        .Should()
                        .OnlyContain(c => c.AppliedTime != null);
                    }
                }
            }
        }
        public async Task Immediately_scheduled_commands_triggered_by_a_scheduled_command_have_their_due_time_set_to_the_causative_command_clock()
        {
            VirtualClock.Start();

            var aggregate = new CommandSchedulerTestAggregate();
            var repository = Configuration.Current
                                          .Repository<CommandSchedulerTestAggregate>();

            await repository.Save(aggregate);

            var scheduler = Configuration.Current.CommandScheduler<CommandSchedulerTestAggregate>();

            var dueTime = Clock.Now().AddMinutes(5);

            await scheduler.Schedule(
                aggregate.Id,
                dueTime: dueTime,
                command: new CommandSchedulerTestAggregate.CommandThatSchedulesAnotherCommandImmediately
                {
                    NextCommandAggregateId = aggregate.Id,
                    NextCommand = new CommandSchedulerTestAggregate.Command
                    {
                        CommandId = Any.CamelCaseName()
                    }
                });

            VirtualClock.Current.AdvanceBy(TimeSpan.FromDays(1));

            using (var db = new CommandSchedulerDbContext())
            {
                foreach (var command in db.ScheduledCommands.Where(c => c.AggregateId == aggregate.Id))
                {
                    command.AppliedTime
                           .IfNotNull()
                           .ThenDo(v => v.Should().BeCloseTo(dueTime, 10));
                }
            }
        }