public async Task Getting_and_storing_projections_and_cursors_can_operate_transactionally_via_a_closure() { BalanceProjection finalProjection = null; var catchup = StreamCatchup.Create(stream); FetchAndSaveProjection <BalanceProjection> fetchAndSaveProjection = (async(id, callAggregatorPipeline) => { using (var transaction = new TransactionScope()) { // e.g. get the projection / cursor from the store var proj = new BalanceProjection { CursorPosition = 5 }; proj = await callAggregatorPipeline(proj); finalProjection = proj; // save the projection / cursor back to the store transaction.Complete(); } }); catchup.Subscribe(new BalanceProjector(), fetchAndSaveProjection); store.WriteEvents(streamId, amount: 100m, howMany: 5); await catchup.RunSingleBatch(); finalProjection.Balance.Should().Be(100m); }
public async Task A_pipeline_can_be_used_to_continue_on_exceptions() { var aggregator = Aggregator .Create <BalanceProjection, IDomainEvent>((projection, events) => { Task.Run(() => { throw new Exception("DRAT!"); }); }) .Pipeline(async(projection, events, next) => { try { return(await next(projection, events)); } catch (Exception) { return(projection); } }); var balanceProjection = new BalanceProjection(); var returnedProjection = await aggregator.Aggregate(balanceProjection, null); balanceProjection.Should().BeSameAs(returnedProjection); }
public async Task An_aggregator_can_be_short_circuited_using_Pipeline_and_returning_rather_than_calling_next() { var wasCalled = false; var aggregator = Aggregator.Create <BalanceProjection, IDomainEvent>((projection, events) => wasCalled = true) .Pipeline(async(projection, events, next) => projection); var balanceProjection = new BalanceProjection(); var returnedProjection = await aggregator.Aggregate(balanceProjection, null); wasCalled.Should().BeFalse(); balanceProjection.Should().BeSameAs(returnedProjection); }
public async Task When_a_stream_has_no_events_after_the_projection_cursor_then_no_data_is_fetched() { var initialProjection = new BalanceProjection { Balance = 321m, CursorPosition = 5 }; var finalProjection = await stream.Aggregate(AccountBalanceProjector(), initialProjection); finalProjection.ShouldBeEquivalentTo(initialProjection, "the projection cursor is past the end of the event stream so no events should be applied"); }
public async Task Projections_can_be_updated_from_a_previously_stored_state() { var projection = new BalanceProjection { Balance = 100m, CursorPosition = 2 }; var balanceProjection = await stream.Aggregate(AccountBalanceProjector(), projection); balanceProjection.Balance .Should() .Be(111m, "the first two items in the sequence should not have been applied, and the prior projection state should have been used"); }
public async Task The_same_projection_is_not_queried_more_than_once_during_a_batch() { var streamId = "hello"; var projection = new BalanceProjection { CursorPosition = 1 }; var getCount = 0; var projectionStore = ProjectionStore.Create <string, BalanceProjection>( get: async key => { if (key.Contains(streamId)) { Console.WriteLine("Get"); Interlocked.Increment(ref getCount); } return(projection); }, put: async(key, p) => { if (streamId == key) { Console.WriteLine("Put"); } projection = p; }); var catchup = StreamCatchup.All(streamSource.StreamPerAggregate()); catchup.Subscribe(new BalanceProjector(), projectionStore); store.WriteEvents(streamId); store.WriteEvents(streamId); store.WriteEvents(streamId); await catchup.RunSingleBatch(); getCount.Should().Be(1); }