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"); }
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); }
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(); } }
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(); }
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 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); }
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!")); }
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(); }
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); }
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); }