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 async Task When_using_Update_then_failed_writes_do_not_interrupt_catchup() { // preload some events for the catchup. replay will hit the barrier on the last one. var order = new Order(); var productName = Any.Paragraph(4); Action addEvent = () => order.Apply(new AddItem { Quantity = 1, ProductName = productName, Price = .01m }); Enumerable.Range(1, 30).ForEach(_ => addEvent()); var repository = new SqlEventSourcedRepository <Order>(new FakeEventBus()); await repository.Save(order); var count = 0; Projector1 projector = null; projector = new Projector1 { OnUpdate = (_, e) => { using (var work = projector.Update()) { var db = work.Resource <ReadModelDbContext>(); if (count++ == 15) { // 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 count.Should().Be(30); }
public async Task When_not_using_Update_then_failed_writes_do_not_interrupt_catchup() { // arrange // preload some events for the catchup. replay will hit the barrier on the last one. var order = new Order(); var productName = Any.Paragraph(3); Action addEvent = () => order.Apply(new AddItem { Quantity = 1, ProductName = productName, Price = .01m }); Enumerable.Range(1, 30).ForEach(_ => addEvent()); var repository = new SqlEventSourcedRepository <Order>(new FakeEventBus()); await repository.Save(order); var count = 0; var projector = new Projector1 { OnUpdate = (work, e) => { using (var db = ReadModelDbContext()) { // throw one exception in the middle if (count++ == 15) { throw new Exception("drat!"); } db.SaveChanges(); } } }; // act using (var catchup = CreateReadModelCatchup(projector)) { await catchup.Run(); } // assert count.Should().Be(30); }
public async Task ReadModelCatchup_only_queries_events_since_the_last_consumed_event_id() { var bus = new FakeEventBus(); var repository = new SqlEventSourcedRepository <Order>(bus); // save the order with no projectors running var order = new Order(); order.Apply(new AddItem { Price = 1m, ProductName = "Widget" }); await repository.Save(order); // subscribe one projector for catchup var projector1 = new Projector1(); using (var catchup = CreateReadModelCatchup(projector1)) { catchup.Progress.ForEachAsync(s => Console.WriteLine(s)); await catchup.Run(); } order.Apply(new AddItem { Price = 1m, ProductName = "Widget" }); await repository.Save(order); // subscribe both projectors var projector2 = new Projector2(); using (var catchup = CreateReadModelCatchup(projector1, projector2)) { catchup.Progress.ForEachAsync(s => Console.WriteLine(s)); await catchup.Run(); } projector1.CallCount.Should().Be(2, "A given event should only be passed to a given projector once"); projector2.CallCount.Should().Be(2, "A projector should be passed events it has not previously seen."); }
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 = 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 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 = 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); } }