예제 #1
0
        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);
            }
        }
예제 #2
0
        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);
            }
        }
예제 #3
0
        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);
            }
        }
예제 #4
0
        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);
            }
        }
예제 #5
0
        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.");
        }
예제 #6
0
        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));
            }
        }
예제 #7
0
        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();
            }
        }
예제 #8
0
 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;
            }));
        }
예제 #10
0
        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);
            }
        }
예제 #11
0
        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.");
        }