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