public SqlCommandScheduler(
     Func <IEventSourcedRepository <TAggregate> > getRepository,
     Func <CommandSchedulerDbContext> createCommandSchedulerDbContext,
     IEventBus eventBus,
     CommandPreconditionVerifier commandPreconditionVerifier)
 {
     if (getRepository == null)
     {
         throw new ArgumentNullException("getRepository");
     }
     if (createCommandSchedulerDbContext == null)
     {
         throw new ArgumentNullException("createCommandSchedulerDbContext");
     }
     if (eventBus == null)
     {
         throw new ArgumentNullException("eventBus");
     }
     if (commandPreconditionVerifier == null)
     {
         throw new ArgumentNullException("commandPreconditionVerifier");
     }
     this.getRepository = getRepository;
     this.createCommandSchedulerDbContext = createCommandSchedulerDbContext;
     this.eventBus = eventBus;
     this.commandPreconditionVerifier = commandPreconditionVerifier;
     consequenter = Consequenter.Create <IScheduledCommand <TAggregate> >(e =>
     {
         Task.Run(() => Schedule(e)).Wait();
     });
 }
        public void Durable_consequenters_can_be_simulated_for_unit_testing_and_receive_messages()
        {
            var consequenter1WasCalled = false;
            var consequenter2WasCalled = false;

            var uselessSettings = new ServiceBusSettings();

            using (ServiceBusDurabilityExtensions.Simulate())
            {
                var consequenter1 = Consequenter.Create <Order.Cancelled>(
                    e => { consequenter1WasCalled = true; });
                var consequenter2 = Consequenter.Create <Order.CreditCardCharged>(
                    e => { consequenter2WasCalled = true; });

                var bus = new InProcessEventBus();
                bus.Subscribe(
                    consequenter1.UseServiceBusForDurability(uselessSettings),
                    consequenter2.UseServiceBusForDurability(uselessSettings));

                bus.PublishAsync(new Order.Cancelled()).Wait();

                consequenter1WasCalled.Should().BeTrue();
                consequenter2WasCalled.Should().BeFalse();
            }
        }
        public void Errors_during_catchup_result_in_retries()
        {
            var consequenter = Consequenter.Create <Order.CreditCardCharged>(e => { })
                               .UseServiceBusForDurability(new ServiceBusSettings
            {
                ConnectionString = "this will never work"
            });

            var errors      = new List <EventHandlingError>();
            var aggregateId = Any.Guid();

            Events.Write(1, i => new Order.CreditCardCharged
            {
                AggregateId = aggregateId
            });

            using (Domain.Configuration.Global.EventBus.Errors.Subscribe(errors.Add))
                using (var catchup = CreateReadModelCatchup <CommandSchedulerDbContext>(consequenter))
                {
                    catchup.Run();

                    errors.Should().Contain(e => e.AggregateId == aggregateId &&
                                            e.Exception.ToString().Contains("excelsior!"));
                }

            // TODO (Errors_during_catchup_result_in_retries) this would require a slightly different catchup mechanism or a way to retry events found in the EventHandlingErrors table
            Assert.Fail("Test not written yet.");
        }
        public async Task GetAggregate_can_be_used_when_no_aggregate_was_previously_sourced()
        {
            var order = new Order()
                        .Apply(new ChangeCustomerInfo
            {
                CustomerName = Any.FullName()
            });

            var repository = CreateRepository <Order>();
            await repository.Save(order);

            Order aggregate   = null;
            var   consquenter = Consequenter.Create <Order.Placed>(e =>
            {
#pragma warning disable 612
                aggregate = e.GetAggregate();
#pragma warning restore 612
            });

            consquenter.HaveConsequences(new Order.Placed
            {
                AggregateId = order.Id
            });

            aggregate.Id.Should().Be(order.Id);
        }
示例#5
0
 public void IsEventHandlerType_returns_true_for_consequenters()
 {
     Consequenter.Create <Order.ItemRemoved>(e => { })
     .GetType()
     .IsEventHandlerType()
     .Should()
     .BeTrue();
 }
示例#6
0
        public void A_named_assigned_using_Named_is_not_lost_on_successive_wraps()
        {
            var name         = Any.CamelCaseName();
            var consequenter = Consequenter.Create <Order.CustomerInfoChanged>(e => { })
                               .Named(name)
                               .WrapAll((e, next) => next(e));

            EventHandler.Name(consequenter).Should().Be(name);
            EventHandler.FullName(consequenter).Should().Be(name);
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="InMemoryCommandScheduler{TAggregate}"/> class.
        /// </summary>
        /// <param name="repository">The repository.</param>
        /// <exception cref="System.ArgumentNullException">repository</exception>
        public InMemoryCommandScheduler(IEventSourcedRepository <TAggregate> repository)
        {
            if (repository == null)
            {
                throw new ArgumentNullException("repository");
            }
            this.repository = repository;

            consequenter = Consequenter.Create <IScheduledCommand <TAggregate> >(e => Schedule(e).Wait());
        }
示例#8
0
        public void EventHandler_FullName_differentiates_anonymous_consequenters_that_were_named_using_Named()
        {
            // deliberately different implementations to avoid compiler inlining -- basically the implementation is going to depend on the anonymous closure types being different
            var anonymousConsequenter1 = Consequenter.Create <Order.Cancelled>(e => Console.WriteLine(e.AggregateId)).Named("one");
            var anonymousConsequenter2 = Consequenter.Create <Order.Cancelled>(e => Console.WriteLine(e.SequenceNumber)).Named("two");

            var consequenter1Name = EventHandler.FullName(anonymousConsequenter1);
            var consequenter2Name = EventHandler.FullName(anonymousConsequenter2);

            consequenter1Name.Should().NotBe(consequenter2Name);
        }
示例#9
0
        public void Named_does_not_prevent_calls_to_inner_handler()
        {
            var callCount = 0;
            var handler   = Consequenter.Create <Order.CreditCardCharged>(e => callCount++)
                            .Named("something");

            bus.Subscribe(handler);

            bus.PublishAsync(new Order.CreditCardCharged()).Wait();

            callCount.Should().Be(1);
        }
示例#10
0
        public async Task GetAggregate_can_be_used_within_a_consequenter_to_access_an_aggregate_without_having_to_re_source()
        {
            // arrange
            var order = new Order()
                        .Apply(new ChangeCustomerInfo
            {
                CustomerName = Any.FullName()
            })
                        .Apply(new AddItem
            {
                ProductName = "Cog",
                Price       = 9.99m
            })
                        .Apply(new AddItem
            {
                ProductName = "Sprocket",
                Price       = 9.99m
            })
                        .Apply(new ProvideCreditCardInfo
            {
                CreditCardNumber          = Any.String(16, 16, Characters.Digits),
                CreditCardCvv2            = "123",
                CreditCardExpirationMonth = "12",
                CreditCardName            = Any.FullName(),
                CreditCardExpirationYear  = "16"
            })
                        .Apply(new SpecifyShippingInfo())
                        .Apply(new Place());

            var repository = CreateRepository <Order>();

            Configuration.Current.UseDependency <IEventSourcedRepository <Order> >(t =>
            {
                throw new Exception("GetAggregate should not be triggering this call.");
            });
            Order aggregate = null;

#pragma warning disable 612
#pragma warning disable 618
            var consquenter = Consequenter.Create <Order.Placed>(e => { aggregate = e.GetAggregate(); });
#pragma warning restore 618
#pragma warning restore 612
            var bus = Configuration.Current.EventBus as FakeEventBus;
            bus.Subscribe(consquenter);

            // act
            await repository.Save(order);

            // assert
            aggregate.Should().Be(order);
        }
示例#11
0
        public async Task When_one_command_triggers_another_command_via_a_consequenter_then_the_second_command_acquires_the_first_commands_clock()
        {
            // arrange
            var orderId         = Any.Guid();
            var customerId      = Any.Guid();
            var bus             = new InProcessEventBus();
            var orderRepository = new InMemoryEventSourcedRepository <Order>(bus: bus);
            await orderRepository.Save(new Order(new CreateOrder(Any.FullName())
            {
                AggregateId = orderId,
                CustomerId = customerId
            }).Apply(new AddItem
            {
                ProductName = Any.Word(),
                Quantity    = 1,
                Price       = Any.Decimal(.01m, 10m)
            }));

            var customerRepository = new InMemoryEventSourcedRepository <CustomerAccount>();
            await customerRepository.Save(new CustomerAccount(customerId).Apply(new ChangeEmailAddress(Any.Email())));

#pragma warning disable 618
            bus.Subscribe(Consequenter.Create <Order.Shipped>(e =>
#pragma warning restore 618
            {
                var order    = orderRepository.GetLatest(e.AggregateId).Result;
                var customer = customerRepository.GetLatest(order.CustomerId).Result;
                customer.Apply(new SendOrderConfirmationEmail(order.OrderNumber));
                customerRepository.Save(customer).Wait();
            }));
            var shipDate = DateTimeOffset.Parse("2014-05-15 01:01:01");
            var ship     = new Ship();

            // act
            using (CommandContext.Establish(ship, Clock.Create(() => shipDate)))
            {
                var order = await orderRepository.GetLatest(orderId);

                order.Apply(ship);
                await orderRepository.Save(order);
            }

            // assert
            var last = (await customerRepository.GetLatest(customerId)).Events().Last();
            last.Should()
            .BeOfType <CustomerAccount.OrderShipConfirmationEmailSent>();
            last.Timestamp.Should().Be(shipDate);
        }
        public void When_the_same_message_is_handled_by_multiple_consequenters_it_is_delivered_to_each_only_once()
        {
            var aggregateId            = Any.Guid();
            var consequenter1WasCalled = new AsyncValue <bool>();
            var consequenter2WasCalled = new AsyncValue <bool>();
            var consequenter1CallCount = 0;
            var consequenter2CallCount = 0;
            var consequenter1          = Consequenter.Create <Order.Fulfilled>(e =>
            {
                if (e.AggregateId == aggregateId)
                {
                    Interlocked.Increment(ref consequenter1CallCount);
                    consequenter1WasCalled.Set(true);
                }
            })
                                         .Named(MethodBase.GetCurrentMethod().Name + "-1")
                                         .UseServiceBusForDurability(settings, configuration: configuration);
            var consequenter2 = Consequenter.Create <Order.Fulfilled>(e =>
            {
                if (e.AggregateId == aggregateId)
                {
                    Interlocked.Increment(ref consequenter2CallCount);
                    consequenter2WasCalled.Set(true);
                }
            })
                                .Named(MethodBase.GetCurrentMethod().Name + "-2")
                                .UseServiceBusForDurability(settings, configuration: configuration);

            using (var catchup = CreateReadModelCatchup <CommandSchedulerDbContext>(consequenter1, consequenter2))
            {
                Events.Write(1, i => new Order.Fulfilled {
                    AggregateId = aggregateId
                });

                catchup.Run();

                // give the service bus messages times to be delivered
                Task.WaitAll(new Task[]
                {
                    consequenter1WasCalled,
                    consequenter2WasCalled
                }, DefaultTimeout());

                consequenter1CallCount.Should().Be(1);
                consequenter2CallCount.Should().Be(1);
            }
        }
        public async Task Catchup_can_be_used_with_intercepted_consequenters_to_queue_event_messages_on_the_service_bus()
        {
            var onCancelledCalled           = new AsyncValue <bool>();
            var onCreatedCalled             = new AsyncValue <bool>();
            var onDeliveredCalled           = new AsyncValue <bool>();
            var anonymousConsequenterCalled = new AsyncValue <bool>();

            var consequenter1 = new TestConsequenter(
                onCancelled: e => onCancelledCalled.Set(true),
                onCreated: e => onCreatedCalled.Set(true),
                onDelivered: e => onDeliveredCalled.Set(true))
                                .UseServiceBusForDurability(settings, configuration: configuration);

            var name          = MethodBase.GetCurrentMethod().Name;
            var consequenter2 = Consequenter.Create <Order.Fulfilled>(e => anonymousConsequenterCalled.Set(true))
                                .Named(name)
                                .UseServiceBusForDurability(settings, configuration: configuration);

            using (var catchup = CreateReadModelCatchup <CommandSchedulerDbContext>(
                       consequenter1,
                       consequenter2))
            {
                Events.Write(1, i => new Order.Created());
                Events.Write(1, i => new Order.Delivered());
                Events.Write(1, i => new Order.Fulfilled());
                Events.Write(1, i => new Order.Cancelled());

                catchup.Run();

                // give the service bus messages times to be delivered
                Task.WaitAll(new Task[]
                {
                    onCancelledCalled,
                    onCreatedCalled,
                    onDeliveredCalled,
                    anonymousConsequenterCalled
                }, DefaultTimeout());

                onCancelledCalled.Result.Should().Be(true);
                onCreatedCalled.Result.Should().Be(true);
                onDeliveredCalled.Result.Should().Be(true);
                anonymousConsequenterCalled.Result.Should().Be(true);
            }
        }
示例#14
0
        public void When_an_exception_is_thrown_by_a_IHaveConsequencesWhen_then_it_continues_to_receive_events()
        {
            int count = 0;

            bus.Subscribe(Consequenter.Create <Order.Paid>(e =>
            {
                count++;
                if (count > 8)
                {
                    throw new Exception("oops!");
                }
            }));

            var events = Enumerable.Range(1, 10).Select(i => new Order.Paid(i)).ToArray();

            using (bus.PublishAsync(events).Subscribe())
            {
                count.Should().Be(10);
            }
        }
        public void Errors_during_event_handling_are_published_back_to_the_global_bus_on_delivery()
        {
            var consequenter = Consequenter.Create <Order.CreditCardCharged>(e => { throw new Exception("whoa that's bad..."); })
                               .UseServiceBusForDurability(settings, configuration: configuration);

            var errors      = new List <EventHandlingError>();
            var aggregateId = Any.Guid();

            Events.Write(1, i => new Order.CreditCardCharged
            {
                AggregateId = aggregateId
            });

            using (configuration.EventBus.Errors.Subscribe(errors.Add))
                using (var catchup = CreateReadModelCatchup <CommandSchedulerDbContext>(consequenter))
                {
                    catchup.Run();

                    errors.Should().Contain(e => e.AggregateId == aggregateId &&
                                            e.Exception.ToString().Contains("whoa that's bad..."));
                }
        }
示例#16
0
        public void EventHandler_Name_returns_something_vaguely_informative_for_anonymous_consequenters()
        {
            var wrapper = Consequenter.Create <Order.ItemAdded>(e => { });

            EventHandler.Name(wrapper).Should().Be("AnonymousConsequenter");
        }