예제 #1
0
        public async Task If_Subscribe_is_called_more_than_once_for_a_given_handler_it_is_not_subscribed_again()
        {
            var callCount = 0;
            var handler   = Projector.Create <Order.ItemAdded>(e => { callCount++; });

            var bus = new InProcessEventBus();

            bus.Subscribe(handler);
            bus.Subscribe(handler);

            await bus.PublishAsync(new Order.ItemAdded());

            callCount.Should().Be(1);
        }
        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();
            }
        }
예제 #3
0
        public void When_multiple_consequenter_handlers_are_chained_then_the_last_added_is_called_first()
        {
            // arrange
            var bus = new InProcessEventBus();

            var handlerCalls = new List <string>();

            // act
            var handler = new TestConsequenter()
                          .WrapAll((e, next) =>
            {
                handlerCalls.Add("c");
                next(e);
            })
                          .WrapAll((e, next) =>
            {
                handlerCalls.Add("b");
                next(e);
            })
                          .WrapAll((e, next) =>
            {
                handlerCalls.Add("a");
                next(e);
            });

            bus.Subscribe(handler);

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

            // assert
            handlerCalls.Should().BeInAscendingOrder();
        }
예제 #4
0
        public void Handler_chains_can_be_specified_for_all_event_types_on_a_single_projector()
        {
            // arrange
            var bus = new InProcessEventBus();
            var createdWasCalled   = false;
            var cancelledWasCalled = false;
            var deliveredWasCalled = false;

            var handledEvents = new List <IEvent>();

            // act
            var handler = new TestProjector(
                onCreated: e => createdWasCalled     = true,
                onCancelled: e => cancelledWasCalled = true,
                onDelivered: e => deliveredWasCalled = true)
                          .WrapAll((e, nextHandler) =>
            {
                handledEvents.Add(e);
                nextHandler(e);
            });

            bus.Subscribe(handler);

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

            // assert
            handledEvents.Count.Should().Be(3);
            createdWasCalled.Should().BeTrue("created was called");
            cancelledWasCalled.Should().BeTrue("cancelled was called");
            deliveredWasCalled.Should().BeTrue("delivered was called");
        }
예제 #5
0
        public async Task When_a_handler_chain_throws_then_subsequent_events_are_still_published()
        {
            // arrange
            var bus    = new InProcessEventBus();
            var errors = new List <EventHandlingError>();

            bus.Errors.Subscribe(errors.Add);
            var callCount = 0;

            // act
            var handler = new TestConsequenter()
                          .WrapAll((e, next) =>
            {
                callCount++;
                if (callCount == 1)
                {
                    throw new Exception("oops!");
                }
            });

            bus.Subscribe(handler);

            await bus.PublishAsync(new Order.Created());

            await bus.PublishAsync(new Order.Created());

            // assert
            callCount.Should().Be(2);
        }
예제 #6
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);
        }
예제 #7
0
 private void IncludeReadModelsNeeding(StorableEvent storedEvent)
 {
     if (unsubscribedReadModelInfos.Count > 0)
     {
         foreach (var readmodelInfo in unsubscribedReadModelInfos.ToArray())
         {
             if (storedEvent.Id >= readmodelInfo.CurrentAsOfEventId + 1)
             {
                 var handler = projectors.Single(p => ReadModelInfo.NameForProjector(p) == readmodelInfo.Name);
                 disposables.Add(bus.Subscribe(handler));
                 unsubscribedReadModelInfos.Remove(readmodelInfo);
                 subscribedReadModelInfos.Add(readmodelInfo);
             }
         }
     }
 }
예제 #8
0
        public void PublishAndSubscribeEventTest()
        {
            InProcessEventBus bus       = new InProcessEventBus();
            TestEvent         testEvent = new TestEvent("some value");

            TestEvent receivedEvent = null;

            bus.Subscribe(new Subscriber(eventEnvelope => receivedEvent = eventEnvelope.GetContent <TestEvent>()));

            bus.Publish(testEvent);

            // sleep because the bus publishes events on another thread
            Thread.Sleep(10);

            receivedEvent.Should().NotBeNull();
            receivedEvent.ShouldBeEquivalentTo(testEvent);
        }
예제 #9
0
        public void When_a_consequenter_that_has_been_chained_throws_then_the_EventHandlingError_Handler_references_the_inner_handler()
        {
            // arrange
            var bus    = new InProcessEventBus();
            var errors = new List <EventHandlingError>();

            bus.Errors.Subscribe(errors.Add);

            // act
            var handler = new TestConsequenter(onCreated: e => { throw new Exception("oops!"); });

            bus.Subscribe(handler);

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

            // assert
            errors.Should().ContainSingle(e => e.Handler is TestConsequenter);
        }
예제 #10
0
        public void InMemoryCommandScheduler_executes_scheduled_commands_immediately_if_no_due_time_is_specified()
        {
            // arrange
            var bus        = new InProcessEventBus();
            var repository = new InMemoryEventSourcedRepository <Order>(bus: bus);
            var scheduler  = new InMemoryCommandScheduler <Order>(repository);

            bus.Subscribe(scheduler);
            var order = CreateOrder();

            // act
            order.Apply(new ShipOn(Clock.Now().Subtract(TimeSpan.FromDays(2))));
            repository.Save(order);

            //assert
            order = repository.GetLatest(order.Id);
            var lastEvent = order.Events().Last();

            lastEvent.Should().BeOfType <Order.Shipped>();
        }
예제 #11
0
        public void When_a_handler_chain_throws_then_an_EventHandlingError_is_published()
        {
            // arrange
            var bus    = new InProcessEventBus();
            var errors = new List <EventHandlingError>();

            bus.Errors.Subscribe(errors.Add);

            // act
            var handler = new TestConsequenter()
                          .WrapAll((e, next) => { throw new Exception("oops!"); });

            bus.Subscribe(handler);

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

            // assert
            errors.Should().ContainSingle(e => e.StreamName == "Order" &&
                                          e.Event.EventName() == "Created" &&
                                          e.Exception.Message.Contains("oops!"));
        }
예제 #12
0
        public void Consequenter_can_be_short_circuited_using_handler_chains()
        {
            // arrange
            var bus = new InProcessEventBus();
            var consequenterWasCalled = false;
            var handlerCalls          = 0;

            // act
            var handler = new TestConsequenter(onCreated: e => consequenterWasCalled = true)
                          .WrapAll((e, next) =>
            {
                handlerCalls++;
                // by not calling next, we short circuit the call to the remaining handler chain
            });

            bus.Subscribe(handler);

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

            // assert
            handlerCalls.Should().Be(1);
            consequenterWasCalled.Should().BeFalse();
        }
예제 #13
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())));
            bus.Subscribe(Consequenter.Create<Order.Shipped>(e =>
            {
                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);
        }
        private long StreamEventsToProjections(ExclusiveEventStoreCatchupQuery query)
        {
            long eventsProcessed = 0;

            foreach (var storedEvent in query.Events)
            {
                eventsProcessed++;

                if (unsubscribedReadModelInfos.Count > 0)
                {
                    foreach (var readmodelInfo in unsubscribedReadModelInfos.ToArray())
                    {
                        if (storedEvent.Id >= readmodelInfo.CurrentAsOfEventId + 1)
                        {
                            var handler = projectors.Single(p => ReadModelInfo.NameForProjector(p) == readmodelInfo.Name);
                            disposables.Add(bus.Subscribe(handler));
                            unsubscribedReadModelInfos.Remove(readmodelInfo);
                            subscribedReadModelInfos.Add(readmodelInfo);
                        }
                    }
                }

                if (cancellationDisposable.IsDisposed)
                {
                    break;
                }

                IEvent @event = null;
                var    now    = Clock.Now();

                try
                {
                    // update projectors
                    @event = storedEvent.ToDomainEvent();

                    if (@event != null)
                    {
                        using (var work = CreateUnitOfWork(@event))
                        {
                            bus.PublishAsync(@event).Wait();

                            var infos = work.Resource <DbContext>().Set <ReadModelInfo>();
                            subscribedReadModelInfos.ForEach(i =>
                            {
                                var eventsRemaining = query.ExpectedNumberOfEvents - eventsProcessed;
                                infos.Attach(i);
                                i.LastUpdated           = now;
                                i.CurrentAsOfEventId    = storedEvent.Id;
                                i.LatencyInMilliseconds = (now - @event.Timestamp).TotalMilliseconds;
                                i.BatchRemainingEvents  = eventsRemaining;
                                if (eventsProcessed == 1)
                                {
                                    i.BatchStartTime   = now;
                                    i.BatchTotalEvents = query.ExpectedNumberOfEvents;
                                }
                                if (i.InitialCatchupStartTime == null)
                                {
                                    i.InitialCatchupStartTime = now;
                                    i.InitialCatchupEvents    = query.ExpectedNumberOfEvents;
                                }
                                if (eventsRemaining == 0 & i.InitialCatchupEndTime == null)
                                {
                                    i.InitialCatchupEndTime = now;
                                }
                            });

                            work.VoteCommit();
                        }
                    }
                    else
                    {
                        throw new SerializationException(string.Format(
                                                             "Deserialization: Event type '{0}.{1}' not found",
                                                             storedEvent.StreamName,
                                                             storedEvent.Type));
                    }
                }
                catch (Exception ex)
                {
                    var error = @event == null
                                    ? SerializationError(ex, storedEvent)
                                    : new Domain.EventHandlingError(ex, @event: @event);

                    ReadModelUpdate.ReportFailure(
                        error,
                        () => CreateReadModelDbContext());
                }

                var status = new ReadModelCatchupStatus
                {
                    BatchCount = query.ExpectedNumberOfEvents,
                    NumberOfEventsProcessed = eventsProcessed,
                    CurrentEventId          = storedEvent.Id,
                    EventTimestamp          = storedEvent.Timestamp,
                    StatusTimeStamp         = now,
                    CatchupName             = Name
                };

                if (status.IsEndOfBatch)
                {
                    // reset the re-entrancy flag
                    running = 0;
                    query.Dispose();
                }

                ReportStatus(status);
            }
            return(eventsProcessed);
        }
예제 #15
0
        public async Task InMemoryCommandScheduler_executes_scheduled_commands_immediately_if_no_due_time_is_specified()
        {
            // arrange
            var bus = new InProcessEventBus();
            var repository = new InMemoryEventSourcedRepository<Order>(bus: bus);
            var scheduler = new InMemoryCommandScheduler<Order>(repository);
            bus.Subscribe(scheduler);
            var order = CreateOrder();

            // act
            order.Apply(new ShipOn(Clock.Now().Subtract(TimeSpan.FromDays(2))));
            await repository.Save(order);

            await scheduler.Done();

            //assert 
            order = await repository.GetLatest(order.Id);
            var lastEvent = order.Events().Last();
            lastEvent.Should().BeOfType<Order.Shipped>();
        }