예제 #1
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");
        }
예제 #2
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);
        }
예제 #3
0
        private static IDisposable SubscribeToReceiveMessages <THandler>(
            IEventHandlerBinder binder,
            THandler handler,
            ServiceBusSettings settings,
            Configuration configuration,
            Dictionary <Type, QueueClient> queues) where THandler : class
        {
            Type eventType = ((dynamic)binder).EventType;

            var queueName = string.Format("{0}_on_{1}.{2}",
                                          EventHandler.Name(handler),
                                          eventType.AggregateTypeForEventType().Name,
                                          eventType.EventName());

            var bus = new InProcessEventBus(errorSubject: (ISubject <EventHandlingError>)configuration.EventBus.Errors);
            var eventBusSubscription = binder.SubscribeToBus(handler, bus);

            var receive = SimulatedReceive;

            if (receive != null)
            {
                var receiveSubscription = receive.Subscribe(e => bus.PublishAsync(e).Subscribe(_ => { }, ex => bus.PublishErrorAsync(new EventHandlingError(ex, @event: e))));
                return(new CompositeDisposable(eventBusSubscription,
                                               receiveSubscription));
            }

            var queueClient = settings.CreateQueueClient(
                queueName,
                settings.ConfigureQueue);

            // starting listening on the queue for incoming events
            queueClient.OnMessage(msg =>
            {
                var storedEvent = msg.GetBody <string>()
                                  .FromJsonTo <StoredEvent>();

                var @event = Serializer.DeserializeEvent(
                    aggregateName: storedEvent.AggregateName,
                    eventName: storedEvent.EventName,
                    body: storedEvent.Body,
                    aggregateId: storedEvent.AggregateId,
                    sequenceNumber: storedEvent.SequenceNumber,
                    timestamp: storedEvent.Timestamp);

                bus.PublishAsync(@event).Subscribe(
                    _ => msg.Complete(),
                    ex => bus.PublishErrorAsync(new EventHandlingError(ex, @event: @event)));
            });

            queues[((dynamic)binder).EventType] = queueClient;

            return(new CompositeDisposable(eventBusSubscription, Disposable.Create(queueClient.Close)));
        }
        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();
            }
        }
예제 #5
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();
        }
예제 #6
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);
        }
예제 #7
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);
        }
예제 #8
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!"));
        }
예제 #9
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();
        }
예제 #10
0
        private async Task <long> StreamEventsToProjections(ExclusiveEventStoreCatchupQuery query)
        {
            long eventsProcessed = 0;

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

                IncludeReadModelsNeeding(storedEvent);

                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))
                        {
                            await bus.PublishAsync(@event);

                            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);
        }
예제 #11
0
        private async Task <IEvent> UpdateProjectorsAndCursors(
            ExclusiveEventStoreCatchupQuery query,
            StorableEvent storedEvent,
            long eventsProcessedOutOfBatch,
            DateTimeOffset now)
        {
            var @event = storedEvent.ToDomainEvent();

            if (@event != null)
            {
                using (var work = CreateUnitOfWork(@event))
                {
                    var errors = new ConcurrentBag <Domain.EventHandlingError>();

                    using (CaptureErrorsFor(@event, into: errors))
                    {
                        await bus.PublishAsync(@event);
                    }

                    var infos = work.Resource <DbContext>().Set <ReadModelInfo>();

                    subscribedReadModelInfos
                    .Where(i => errors.All(err => err.Handler != i.Handler))
                    .ForEach(i =>
                    {
                        infos.Attach(i);
                        i.LastUpdated           = now;
                        i.CurrentAsOfEventId    = storedEvent.Id;
                        i.LatencyInMilliseconds = (now - @event.Timestamp).TotalMilliseconds;
                        i.BatchRemainingEvents  = query.BatchMatchedEventCount - eventsProcessedOutOfBatch;

                        if (eventsProcessedOutOfBatch == 1)
                        {
                            i.BatchStartTime   = now;
                            i.BatchTotalEvents = query.BatchMatchedEventCount;
                        }

                        if (i.InitialCatchupStartTime == null)
                        {
                            i.InitialCatchupStartTime   = now;
                            i.InitialCatchupTotalEvents = eventStoreTotalCount;
                        }

                        if (i.InitialCatchupEndTime == null)
                        {
                            if (i.CurrentAsOfEventId >= initialCatchupIsDoneAfterEventId)
                            {
                                i.InitialCatchupEndTime         = now;
                                i.InitialCatchupRemainingEvents = 0;
                            }
                            else
                            {
                                // initial catchup is still in progress
                                i.InitialCatchupRemainingEvents = query.TotalMatchedEventCount -
                                                                  eventsProcessedOutOfBatch;
                            }
                        }
                    });

                    work.VoteCommit();
                }
            }
            else
            {
                throw new SerializationException($"Deserialization: Event type '{storedEvent.StreamName}.{storedEvent.Type}' not found");
            }

            return(@event);
        }