public void Report_projections_can_be_created_using_EventHandlerBase_including_dynamic_event_types() { var projector = new OrderTallyProjector(); Events.Write(1, _ => new Order.Cancelled()); Events.Write(20, _ => new Order.Fulfilled()); Events.Write(4, _ => new Order.Misdelivered()); using (var catchup = CreateReadModelCatchup(projector)) { catchup.Run(); } using (var db = new ReadModelDbContext()) { db.Set <OrderTally>() .Single(t => t.Status == "Fulfilled") .Count .Should() .Be(20); db.Set <OrderTally>() .Single(t => t.Status == "Misdelivered") .Count .Should() .Be(4); } }
public void Report_projections_can_be_created_using_EventHandlerBase_over_multiple_event_types() { var projector = new OrderTallyProjector(); Events.Write(20, _ => new Order.Cancelled()); Events.Write(20, _ => new Order.Delivered()); Events.Write(50, _ => new Order.Placed()); using (var catchup = CreateReadModelCatchup(projector)) { catchup.Run(); } using (var db = new ReadModelDbContext()) { db.Set <OrderTally>() .Single(t => t.Status == "Canceled") .Count .Should() .Be(20); db.Set <OrderTally>() .Single(t => t.Status == "Delivered") .Count .Should() .Be(20); db.Set <OrderTally>() .Single(t => t.Status == "Pending") .Count .Should() .Be(10); } }
public void Report_projections_can_be_created_using_anonymous_projectors_over_multiple_event_types() { var projector = Projector.Combine( Projector.CreateFor <Placed>(e => { using (var db1 = new ReadModelDbContext()) { db1.OrderTallyByStatus(OrderTally.OrderStatus.Pending).Count++; db1.SaveChanges(); } }), Projector.CreateFor <Cancelled>(e => { using (var db2 = new ReadModelDbContext()) { db2.OrderTallyByStatus(OrderTally.OrderStatus.Canceled).Count++; db2.OrderTallyByStatus(OrderTally.OrderStatus.Pending).Count--; db2.SaveChanges(); } }), Projector.CreateFor <Delivered>(e => { using (var db3 = new ReadModelDbContext()) { db3.OrderTallyByStatus(OrderTally.OrderStatus.Delivered).Count++; db3.OrderTallyByStatus(OrderTally.OrderStatus.Pending).Count--; db3.SaveChanges(); } })).Named("Order status report"); Events.Write(20, _ => new Order.Cancelled()); Events.Write(20, _ => new Order.Delivered()); Events.Write(50, _ => new Order.Placed()); using (var catchup = CreateReadModelCatchup(projector)) { catchup.Run(); } using (var db = new ReadModelDbContext()) { db.Set <OrderTally>() .Single(t => t.Status == "Canceled") .Count .Should() .Be(20); db.Set <OrderTally>() .Single(t => t.Status == "Delivered") .Count .Should() .Be(20); db.Set <OrderTally>() .Single(t => t.Status == "Pending") .Count .Should() .Be(10); } }
public async Task When_a_projector_update_fails_then_an_entry_is_added_to_EventHandlingErrors() { // arrange var errorMessage = Any.Paragraph(10); var productName = Any.Paragraph(); var projector = new Projector <Order.ItemAdded>(() => new ReadModelDbContext()) { OnUpdate = (work, e) => { throw new Exception(errorMessage); } }; var order = new Order(); var repository = new SqlEventSourcedRepository <Order>(); order.Apply(new AddItem { Price = 1m, ProductName = productName }); await repository.Save(order); // act using (var catchup = CreateReadModelCatchup(projector)) { await catchup.Run(); } // assert using (var db = new ReadModelDbContext()) { var error = db.Set <EventHandlingError>().Single(e => e.AggregateId == order.Id); error.StreamName.Should().Be("Order"); error.EventTypeName.Should().Be("ItemAdded"); error.SerializedEvent.Should().Contain(productName); error.Error.Should().Contain(errorMessage); } }
public void Projection_write_speed_without_unit_of_work() { var eventsRead = 0; var projector1 = Projector.Create <IEvent>(e => { using (var db = new ReadModelDbContext()) { db.Set <ProductInventory>().Add(new ProductInventory { ProductName = Guid.NewGuid().ToString(), QuantityInStock = Any.Int(1, 5), QuantityReserved = 0 }); db.SaveChanges(); eventsRead++; } }).Named(MethodBase.GetCurrentMethod().Name + ":projector1"); using (var catchup = new ReadModelCatchup(projector1) { StartAtEventId = startAtEventId }) { catchup.Run(); } Console.WriteLine(new { eventsRead }); // TODO: (Write_speed_without_unit_of_work) write test Assert.Fail("Test not written yet."); }
public async Task When_using_Update_then_failed_writes_are_logged_to_EventHandlingErrors() { // preload some events for the catchup. replay will hit the barrier on the last one. var order = new Order(); var productName = Any.Paragraph(4); order.Apply(new AddItem { Quantity = 1, ProductName = productName, Price = .01m }); var repository = new SqlEventSourcedRepository <Order>(new FakeEventBus()); await repository.Save(order); Projector1 projector = null; projector = new Projector1 { OnUpdate = (_, e) => { using (var work = projector.Update()) { var db = work.Resource <ReadModelDbContext>(); // do something that will trigger a db exception when the UnitOfWork is committed var inventory = db.Set <ProductInventory>(); inventory.Add(new ProductInventory { ProductName = e.ProductName, QuantityReserved = e.Quantity }); inventory.Add(new ProductInventory { ProductName = e.ProductName, QuantityReserved = e.Quantity }); work.VoteCommit(); } } }; // act using (var catchup = CreateReadModelCatchup(projector)) { await catchup.Run(); } // assert using (var db = new ReadModelDbContext()) { var error = db.Set <EventHandlingError>().Single(e => e.AggregateId == order.Id); error.Error.Should() .Contain( string.Format( "Violation of PRIMARY KEY constraint 'PK_dbo.ProductInventories'. Cannot insert duplicate key in object 'dbo.ProductInventories'. The duplicate key value is ({0})", productName)); } }
public override void SetUp() { Console.WriteLine("ReportingProjectorTests.SetUp"); base.SetUp(); // reset order tallies using (var db = new ReadModelDbContext()) { var tallies = db.Set <OrderTally>(); tallies.ToArray().ForEach(t => tallies.Remove(t)); db.SaveChanges(); } }
private void ResetReadModelInfo() { using (var db = new ReadModelDbContext()) { foreach (var info in db.Set <ReadModelInfo>()) { info.InitialCatchupStartTime = null; info.InitialCatchupEndTime = null; info.BatchRemainingEvents = 0; info.BatchTotalEvents = 0; info.BatchStartTime = null; } db.SaveChanges(); } }
public static OrderTally OrderTallyByStatus( this ReadModelDbContext db, OrderTally.OrderStatus status) { var statusString = status.ToString(); var dbSet = db.Set <OrderTally>(); return(dbSet .SingleOrDefault(o => o.Status == statusString) .IfNotNull() .Else(() => { var tally = new OrderTally { Status = statusString }; dbSet.Add(tally); return tally; })); }
public async Task Events_that_cannot_be_deserialized_to_the_expected_type_are_logged_as_EventHandlingErrors() { var badEvent = new StorableEvent { Actor = Any.Email(), StreamName = typeof(Order).Name, Type = typeof(Order.ItemAdded).Name, Body = new { Price = "oops this is not a number" }.ToJson(), SequenceNumber = Any.PositiveInt(), AggregateId = Any.Guid(), UtcTime = DateTime.UtcNow }; using (var eventStore = new EventStoreDbContext()) { eventStore.Events.Add(badEvent); await eventStore.SaveChangesAsync(); } using (var catchup = CreateReadModelCatchup(new Projector1())) { await catchup.Run(); } using (var readModels = new ReadModelDbContext()) { var failure = readModels.Set <EventHandlingError>() .OrderByDescending(e => e.Id) .First(e => e.AggregateId == badEvent.AggregateId); failure.Error.Should().Contain("JsonReaderException"); failure.SerializedEvent.Should().Contain(badEvent.Body); failure.Actor.Should().Be(badEvent.Actor); failure.OriginalId.Should().Be(badEvent.Id); failure.AggregateId.Should().Be(badEvent.AggregateId); failure.SequenceNumber.Should().Be(badEvent.SequenceNumber); failure.StreamName.Should().Be(badEvent.StreamName); failure.EventTypeName.Should().Be(badEvent.Type); } }
public async Task Insertion_of_new_events_during_catchup_does_not_interrupt_catchup() { var barrier = new Barrier(2); // preload some events for the catchup. replay will hit the barrier on the last one. var order = new Order(); Action addEvent = () => order.Apply(new AddItem { Quantity = 1, ProductName = "Penny candy", Price = .01m }); Enumerable.Range(1, 100).ForEach(_ => addEvent()); var repository = new SqlEventSourcedRepository <Order>(new FakeEventBus()); await repository.Save(order); // queue the catchup on a background task #pragma warning disable 4014 // don't await Task.Run(() => #pragma warning restore 4014 { var projector = new Projector1 { OnUpdate = (work, e) => { if (e.SequenceNumber == 10) { Console.WriteLine("pausing read model catchup"); barrier.SignalAndWait(MaxWaitTime); //1 barrier.SignalAndWait(MaxWaitTime); //2 Console.WriteLine("resuming read model catchup"); } } }; using (var db = new EventStoreDbContext()) using (var catchup = CreateReadModelCatchup(projector)) { var events = db.Events.Where(e => e.Id > HighestEventId); Console.WriteLine(string.Format("starting read model catchup for {0} events", events.Count())); catchup.Run().Wait(); Console.WriteLine("done with read model catchup"); barrier.SignalAndWait(MaxWaitTime); //3 } }); Console.WriteLine("queued read model catchup task"); barrier.SignalAndWait(MaxWaitTime); //1 new EventStoreDbContext().DisposeAfter(c => { Console.WriteLine("adding one more event, bypassing read model tracking"); c.Events.Add(new Order.ItemAdded { AggregateId = Guid.NewGuid(), SequenceNumber = 1 }.ToStorableEvent()); c.SaveChanges(); Console.WriteLine("done adding one more event"); }); barrier.SignalAndWait(MaxWaitTime); //2 barrier.SignalAndWait(MaxWaitTime); //3 // check that everything worked: var projector2 = new Projector1(); var projectorName = ReadModelInfo.NameForProjector(projector2); using (var readModels = new ReadModelDbContext()) { var readModelInfo = readModels.Set <ReadModelInfo>().Single(i => i.Name == projectorName); readModelInfo.CurrentAsOfEventId.Should().Be(HighestEventId + 101); using (var catchup = CreateReadModelCatchup(projector2)) { await catchup.Run(); } readModels.Entry(readModelInfo).Reload(); readModelInfo.CurrentAsOfEventId.Should().Be(HighestEventId + 102); } }
public void Projection_write_speed_without_unit_of_work() { var eventsRead = 0; var projector1 = Projector.Create<IEvent>(e => { using (var db = new ReadModelDbContext()) { db.Set<ProductInventory>().Add(new ProductInventory { ProductName = Guid.NewGuid().ToString(), QuantityInStock = Any.Int(1, 5), QuantityReserved = 0 }); db.SaveChanges(); eventsRead++; } }).Named(MethodBase.GetCurrentMethod().Name + ":projector1"); using (var catchup = new ReadModelCatchup(projector1) { StartAtEventId = startAtEventId }) { catchup.Run(); } Console.WriteLine(new { eventsRead }); // TODO: (Write_speed_without_unit_of_work) write test Assert.Fail("Test not written yet."); }