public static void SetReadModelTrackingTo(this DbContext db, long toEventId, params object[] addForProjectors) { // forward all existing read model tracking foreach (var readModelInfo in db.Set <ReadModelInfo>()) { readModelInfo.CurrentAsOfEventId = toEventId; } foreach (var projector in addForProjectors) { var projectorName = ReadModelInfo.NameForProjector(projector); db.Set <ReadModelInfo>().AddOrUpdate( i => i.Name, new ReadModelInfo { Name = projectorName, CurrentAsOfEventId = toEventId }); } db.SaveChanges(); }
public async Task Database_command_timeouts_during_catchup_do_not_interrupt_catchup() { // reset read model tracking to 0 ReadModelDbContext().DisposeAfter(c => { var projectorName = ReadModelInfo.NameForProjector(new Projector <Order.CustomerInfoChanged>()); c.Set <ReadModelInfo>() .SingleOrDefault(i => i.Name == projectorName) .IfNotNull() .ThenDo(i => { i.CurrentAsOfEventId = 0; }); c.SaveChanges(); }); var exceptions = new Stack <Exception>(Enumerable.Range(1, 2) .Select(_ => new InvalidOperationException("Invalid attempt to call IsDBNull when reader is closed."))); var count = 0; var flakyEvents = new FlakyEventStream( Enumerable.Range(1, 1000) .Select(i => new StorableEvent { AggregateId = Any.Guid(), Body = new Order.CustomerInfoChanged { CustomerName = i.ToString() }.ToJson(), SequenceNumber = i, StreamName = typeof(Order).Name, Timestamp = DateTimeOffset.Now, Type = typeof(Order.CustomerInfoChanged).Name, Id = i }).ToArray(), startFlakingOnEnumeratorNumber: 2, doSomethingFlaky: i => { if (count++ > 50) { count = 0; if (exceptions.Any()) { throw exceptions.Pop(); } } }); var names = new HashSet <string>(); var projector = new Projector <Order.CustomerInfoChanged> { OnUpdate = (work, e) => names.Add(e.CustomerName) }; using (var catchup = CreateReadModelCatchup(projector)) { await catchup.Run(); } projector.CallCount.Should().Be(1000); names.Count.Should().Be(1000); }
public void Rolling_catchup_can_be_run_based_on_event_store_polling() { var numberOfEvents = 50; Console.WriteLine("writing " + numberOfEvents + " starting at " + HighestEventId); // start the catchup in polling mode Projector <Order.ItemAdded> projector = null; var reading = Task.Run(() => { projector = new Projector <Order.ItemAdded>(() => new ReadModels1DbContext()); using (var catchup = CreateReadModelCatchup <ReadModels1DbContext>(projector).PollEventStore()) { catchup.Progress .Do(s => Console.WriteLine(s)) .FirstAsync(s => s.IsEndOfBatch && s.CurrentEventId == numberOfEvents + HighestEventId) .Timeout(DefaultTimeout) .Wait(); } }); // now start writing a bunch of new events var writing = Task.Run(() => Enumerable.Range(1, numberOfEvents).ForEach(_ => { // add a little variation into the intervals at which new events are written Thread.Sleep(Any.PositiveInt(1000)); Events.Write(1); })); writing.Wait(); reading.Wait(); using (var db = new ReadModels1DbContext()) { var readModelInfoName = ReadModelInfo.NameForProjector(projector); db.Set <ReadModelInfo>() .Single(i => i.Name == readModelInfoName) .CurrentAsOfEventId .Should() .Be(HighestEventId + numberOfEvents); } }
public void Events_committed_to_the_event_store_are_caught_up_by_multiple_independent_read_model_stores() { var productName = Any.Paragraph(4); var projector1 = new Projector <Order.ItemAdded>(() => new ReadModels1DbContext()) { OnUpdate = (work, e) => new ReadModels1DbContext().DisposeAfter(db => UpdateReservedInventory(db, e)) }; var projector2 = new Projector <Order.ItemAdded>(() => new ReadModels2DbContext()) { OnUpdate = (work, e) => new ReadModels2DbContext().DisposeAfter(db => UpdateReservedInventory(db, e)) }; var numberOfEvents = Any.Int(10, 50); using (var disposables = new CompositeDisposable()) using (var catchup1 = CreateReadModelCatchup <ReadModels1DbContext>(projector1)) using (var catchup2 = CreateReadModelCatchup <ReadModels2DbContext>(projector2)) { catchup1.Progress.ForEachAsync(p => Console.WriteLine("catchup1: " + p)); catchup2.Progress.ForEachAsync(p => Console.WriteLine("catchup2: " + p)); Action <string, ThreadStart> startThread = (name, start) => { var thread = new Thread(() => { Console.WriteLine("starting thread (" + Thread.CurrentThread.ManagedThreadId + ")"); start(); Console.WriteLine("ended thread (" + Thread.CurrentThread.ManagedThreadId + ")"); }); thread.Name = name; thread.Start(); disposables.Add(Disposable.Create(thread.Abort)); }; Events.Write(numberOfEvents, i => new Order.ItemAdded { ProductName = productName, Quantity = 1, AggregateId = Any.Guid() }); // TODO: (Events_committed_to_the_event_store_are_caught_up_by_multiple_independent_read_model_stores) is this leading to intermittent test failures by leaving a dangling app lock? startThread("catchup1", () => { catchup1.Run(); catchup1.Dispose(); }); startThread("catchup2", () => { catchup2.Run(); catchup2.Dispose(); }); Console.WriteLine("Waiting on catchups to complete"); // wait on both catchups to complete catchup1 .Progress .Merge(catchup2.Progress) .Where(p => p.IsEndOfBatch) .Take(2) .Timeout(DefaultTimeout) .Wait(); } Action <DbContext> verify = db => { var readModelInfoName = ReadModelInfo.NameForProjector(projector1); var readModelInfos = db.Set <ReadModelInfo>(); Console.WriteLine(new { readModelInfos }.ToLogString()); readModelInfos .Single(i => i.Name == readModelInfoName) .CurrentAsOfEventId .Should() .Be(HighestEventId + numberOfEvents); var productInventories = db.Set <ProductInventory>(); Console.WriteLine(new { productInventories }.ToLogString()); productInventories .Single(pi => pi.ProductName == productName) .QuantityReserved .Should() .Be(numberOfEvents); }; Console.WriteLine("verifying ReadModels1DbContext..."); new ReadModels1DbContext().DisposeAfter(r => verify(r)); Console.WriteLine("verifying ReadModels2DbContext..."); new ReadModels2DbContext().DisposeAfter(r => verify(r)); }
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); } }