public async Task When_one_projection_fails_its_cursor_is_not_advanced_while_other_projections_cursors_are_advanced() { var projections = new InMemoryProjectionStore <BalanceProjection>(); // first catch up all the projections var stream = streamSource.StreamPerAggregate().Trace(); var catchup = StreamCatchup.All(stream); var initialSubscription = catchup.Subscribe(new BalanceProjector(), projections); await catchup.RunUntilCaughtUp(); initialSubscription.Dispose(); // write some additional events var streamIdsWithoutErrors = streamIds.Take(5).ToList(); var streamIdsWithErrors = streamIds.Skip(5).Take(5).ToList(); foreach (var streamId in streamIdsWithoutErrors.Concat(streamIdsWithErrors)) { store.WriteEvents(streamId, howMany: 10); } // subscribe a flaky projector catchup.Subscribe(new BalanceProjector() .Pipeline(async(projection, batch, next) => { var aggregateId = batch.Select(i => i.AggregateId).First(); if (streamIdsWithErrors.Contains(aggregateId)) { throw new Exception("oops"); } await next(projection, batch); }), projections.AsHandler(), e => e.Continue()); await catchup.RunSingleBatch(); var projectionsWithoutErrors = streamIdsWithoutErrors.Select( id => projections.Get(id).Result); var projectionsWithErrors = streamIdsWithErrors.Select( id => projections.Get(id).Result); foreach (var projection in projectionsWithoutErrors) { projection.CursorPosition.Should().Be(11); projection.Balance.Should().Be(11); } foreach (var projection in projectionsWithErrors) { projection.CursorPosition.Should().Be(1); projection.Balance.Should().Be(1); } }
public async Task When_projections_are_cursors_then_catchup_does_not_replay_previously_seen_events() { var projectionStore = new InMemoryProjectionStore <BalanceProjection>(); var eventsAggregated = new List <IDomainEvent>(); var catchup = StreamCatchup.All(streamSource.StreamPerAggregate(), batchSize: 100); catchup.Subscribe(new BalanceProjector() .Trace((p, es) => eventsAggregated.AddRange(es)), projectionStore); await catchup.RunUntilCaughtUp(); var streamId = streamIds.First(); store.WriteEvents(streamId, 100m); var cursor = await catchup.RunUntilCaughtUp(); cursor.Position .Should() .Be("101"); var balanceProjection = await projectionStore.Get(streamId); balanceProjection.Balance.Should().Be(101); balanceProjection.CursorPosition.Should().Be(2); eventsAggregated.Count.Should().Be(101); }
public async Task GuidQueryPartitioner_partitions_guids_fairly() { var totalNumberOfGuids = 1000; var numberOfPartitions = 50; var partitions = Partition.AllGuids() .Among(numberOfPartitions); var guids = Enumerable.Range(1, totalNumberOfGuids).Select(_ => Guid.NewGuid()).ToArray(); var partitioned = Stream.Partitioned <Guid, int, Guid>( async(q, p) => guids.Where(g => g.IsWithinPartition(p)), advanceCursor: (q, b) => q.Cursor.AdvanceTo(totalNumberOfGuids)); var aggregator = Aggregator.Create <Projection <HashSet <Guid>, int>, Guid>((p, b) => { if (p.Value == null) { p.Value = new HashSet <Guid>(); } foreach (var guid in b) { p.Value.Add(guid); } }); var store = new InMemoryProjectionStore <Projection <HashSet <Guid>, int> >(); await Task.WhenAll(partitions.Select(async partition => { var stream = await partitioned.GetStream(partition); var catchup = StreamCatchup.Create(stream, batchSize: int.MaxValue); catchup.Subscribe(aggregator, store); await catchup.RunSingleBatch(); var projection = await store.Get(stream.Id); Console.WriteLine(partition + ": " + projection.Value.Count); })); var approximateGuidsPerPartition = totalNumberOfGuids / numberOfPartitions; var tolerance = (int)(totalNumberOfGuids * .12); store.Sum(p => p.Value.Count).Should().Be(totalNumberOfGuids); store.ToList().ForEach(projection => { projection.Value .Count .Should() .BeInRange(approximateGuidsPerPartition - tolerance, approximateGuidsPerPartition + tolerance); }); }
public async Task GuidQueryPartitioner_partitions_guids_fairly() { var totalNumberOfGuids = 1000; var numberOfPartitions = 50; var partitions = Partition.AllGuids() .Among(numberOfPartitions); var guids = Enumerable.Range(1, totalNumberOfGuids).Select(_ => Guid.NewGuid()).ToArray(); var partitioned = Stream.Partitioned<Guid, int, Guid>( async (q, p) => guids.Where(g => g.IsWithinPartition(p)), advanceCursor: (q, b) => q.Cursor.AdvanceTo(totalNumberOfGuids)); var aggregator = Aggregator.Create<Projection<HashSet<Guid>, int>, Guid>((p, b) => { if (p.Value == null) { p.Value = new HashSet<Guid>(); } foreach (var guid in b) { p.Value.Add(guid); } }); var store = new InMemoryProjectionStore<Projection<HashSet<Guid>, int>>(); await Task.WhenAll(partitions.Select(async partition => { var stream = await partitioned.GetStream(partition); var catchup = StreamCatchup.Create(stream, batchSize: int.MaxValue); catchup.Subscribe(aggregator, store); await catchup.RunSingleBatch(); var projection = await store.Get(stream.Id); Console.WriteLine(partition + ": " + projection.Value.Count); })); var approximateGuidsPerPartition = totalNumberOfGuids/numberOfPartitions; var tolerance = (int) (totalNumberOfGuids*.12); store.Sum(p => p.Value.Count).Should().Be(totalNumberOfGuids); store.ToList().ForEach(projection => { projection.Value .Count .Should() .BeInRange(approximateGuidsPerPartition - tolerance, approximateGuidsPerPartition + tolerance); }); }
public async Task When_projections_are_cursors_then_catchup_does_not_replay_previously_seen_events() { var projectionStore = new InMemoryProjectionStore<BalanceProjection>(); var eventsAggregated = new List<IDomainEvent>(); var catchup = StreamCatchup.Create(stream, batchCount: 100); catchup.Subscribe(new BalanceProjector().Trace((p, es) => eventsAggregated.AddRange(es)), projectionStore); await catchup.RunUntilCaughtUp(); store.WriteEvents(streamId, howMany: 100); var cursor = await catchup.RunUntilCaughtUp(); cursor.Position .Should() .Be(101); var balanceProjection = await projectionStore.Get(streamId); balanceProjection.Balance.Should().Be(101); balanceProjection.CursorPosition.Should().Be(101); eventsAggregated.Count.Should().Be(101); }
public async Task GuidQueryPartitioner_partitions_guids_fairly() { var totalNumberOfGuids = 1000; var numberOfPartitions = 50; var partitions = StreamQuery.Partition( Guid.Empty, Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff")) .Among(numberOfPartitions); var guids = Enumerable.Range(1, totalNumberOfGuids).Select(_ => Guid.NewGuid()).ToArray(); var partitioner = Stream.Partition<Guid, int, Guid>( async (q, p) => guids.Where(g => new SqlGuid(g).CompareTo(new SqlGuid(p.LowerBoundExclusive)) > 0 && new SqlGuid(g).CompareTo(new SqlGuid(p.UpperBoundInclusive)) <= 0), advanceCursor: (q, b) => q.Cursor.AdvanceTo(totalNumberOfGuids)); var aggregator = Aggregator.Create<Projection<HashSet<Guid>, int>, Guid>((p, b) => { if (p.Value == null) { p.Value = new HashSet<Guid>(); } foreach (var guid in b) { p.Value.Add(guid); } }); var store = new InMemoryProjectionStore<Projection<HashSet<Guid>, int>>(); await Task.WhenAll(partitions.Select(async partition => { var stream = await partitioner.GetStream(partition); var catchup = StreamCatchup.Create(stream, batchCount: int.MaxValue); catchup.Subscribe(aggregator, store); await catchup.RunSingleBatch(); await Task.Delay(500); var projection = await store.Get(stream.Id); Console.WriteLine(partition + ": " + projection.Value.Count); })); var approximateGuidsPerPartition = totalNumberOfGuids/numberOfPartitions; var tolerance = (int) (totalNumberOfGuids*.12); Console.WriteLine("\nMissing guids: "); foreach (var guid in guids.Where(g => !store.Any(p => p.Value.Contains(g)))) { Console.WriteLine(" " + guid); } store.Sum(p => p.Value.Count).Should().Be(totalNumberOfGuids); store.ToList().ForEach(projection => { projection.Value .Count .Should() .BeInRange(approximateGuidsPerPartition - tolerance, approximateGuidsPerPartition + tolerance); }); }
public async Task When_one_projection_fails_its_cursor_is_not_advanced_while_other_projections_cursors_are_advanced() { var projections = new InMemoryProjectionStore<BalanceProjection>(); // first catch up all the projections var stream = streamSource.StreamPerAggregate().Trace(); var catchup = StreamCatchup.All(stream); var initialSubscription = catchup.Subscribe(new BalanceProjector(), projections); await catchup.RunUntilCaughtUp(); initialSubscription.Dispose(); // write some additional events var streamIdsWithoutErrors = streamIds.Take(5).ToList(); var streamIdsWithErrors = streamIds.Skip(5).Take(5).ToList(); foreach (var streamId in streamIdsWithoutErrors.Concat(streamIdsWithErrors)) { store.WriteEvents(streamId, howMany: 10); } // subscribe a flaky projector catchup.Subscribe(new BalanceProjector() .Pipeline(async (projection, batch, next) => { var aggregateId = batch.Select(i => i.AggregateId).First(); if (streamIdsWithErrors.Contains(aggregateId)) { throw new Exception("oops"); } await next(projection, batch); }), projections.AsHandler(), e => e.Continue()); await catchup.RunSingleBatch(); var projectionsWithoutErrors = streamIdsWithoutErrors.Select( id => projections.Get(id).Result); var projectionsWithErrors = streamIdsWithErrors.Select( id => projections.Get(id).Result); foreach (var projection in projectionsWithoutErrors) { projection.CursorPosition.Should().Be(11); projection.Balance.Should().Be(11); } foreach (var projection in projectionsWithErrors) { projection.CursorPosition.Should().Be(1); projection.Balance.Should().Be(1); } }