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 One_stream_can_transparently_delegate_to_another() { var upstream = NEventStoreStream.AllEvents(store); store.WriteEvents(i => new AccountOpened(), 100); var projection = new Projection <int, string>(); var dependentStream = Stream.Create <int, string>( async q => { var mapped = upstream.Map(e => new[] { e.Count() }); var batch = await mapped.Fetch(q); return(batch); }); var catchup = StreamCatchup.Create(dependentStream, batchSize: 50); FetchAndSaveProjection <Projection <int, string> > manageProjection = async(id, aggregate) => { await aggregate(projection); }; catchup.Subscribe(async(p, b) => { p.Value += b.Sum(); return(p); }, manageProjection); await catchup.RunSingleBatch(); Console.WriteLine(projection.ToLogString()); projection.Value.Should().Be(50); projection.CursorPosition.Should().Be("50"); }
/// <summary> /// Subscribes the specified aggregator to a catchup. /// </summary> /// <typeparam name="TProjection">The type of the projection.</typeparam> /// <typeparam name="TData">The type of the stream's data.</typeparam> /// <typeparam name="TCursor">The type of the cursor.</typeparam> /// <param name="catchup">The catchup.</param> /// <param name="aggregator">The aggregator.</param> /// <returns>A disposable that, when disposed, unsubscribes the aggregator.</returns> public static IDisposable Subscribe <TProjection, TData, TCursor>( this IStreamCatchup <TData, TCursor> catchup, IStreamAggregator <TProjection, TData> aggregator, FetchAndSaveProjection <TProjection> manageProjection, HandleAggregatorError <TProjection> onError = null) { return(catchup.SubscribeAggregator(aggregator, manageProjection, onError)); }
/// <summary> /// Creates a multiple-stream catchup. /// </summary> /// <typeparam name="TData">The type of the stream's data.</typeparam> /// <typeparam name="TUpstreamCursor">The type of the upstream cursor.</typeparam> /// <typeparam name="TDownstreamCursor">The type of the downstream cursor.</typeparam> /// <param name="stream">The stream.</param> /// <param name="batchSize">The number of items to retrieve from the stream per batch.</param> public static IStreamCatchup <TData, TUpstreamCursor> All <TData, TUpstreamCursor, TDownstreamCursor>( IStream <IStream <TData, TDownstreamCursor>, TUpstreamCursor> stream, FetchAndSaveProjection <ICursor <TUpstreamCursor> > manageCursor, int?batchSize = null) { var upstreamCatchup = new SingleStreamCatchup <IStream <TData, TDownstreamCursor>, TUpstreamCursor>(stream, batchSize: batchSize); return(new MultiStreamCatchup <TData, TUpstreamCursor, TDownstreamCursor>( upstreamCatchup, manageCursor)); }
/// <summary> /// Distributes a stream catchup the among one or more partitions using a specified distributor. /// </summary> /// <remarks>If no distributor is provided, then distribution is done in-process.</remarks> public static IStreamCatchup <TData, TCursor> DistributeAmong <TData, TCursor, TPartition>( this IPartitionedStream <TData, TCursor, TPartition> streams, IEnumerable <IStreamQueryPartition <TPartition> > partitions, int?batchSize = null, FetchAndSaveProjection <ICursor <TCursor> > cursorPerPartition = null, IDistributor <IStreamQueryPartition <TPartition> > distributor = null) { return(new DistributedStreamCatchup <TData, TCursor, TPartition>( streams, partitions, batchSize, cursorPerPartition, distributor)); }
public static FetchAndSaveProjection <TProjection> Catch <TProjection>( this FetchAndSaveProjection <TProjection> fetchAndSaveProjection, HandleAggregatorError <TProjection> onError) { return(async(id, aggregate) => { Exception innerException = null; try { await fetchAndSaveProjection(id, async projection => { var resultingProjection = default(TProjection); try { resultingProjection = await aggregate(projection); } catch (Exception exception) { var error = CheckErrorHandler(onError, exception, projection); if (!error.ShouldContinue) { throw; } innerException = exception; return error.Projection; } return resultingProjection; }); } catch (Exception exception) { if (exception != innerException) { var error = CheckErrorHandler(onError, exception); if (!error.ShouldContinue) { throw; } } } }); }
public IDisposable SubscribeAggregator <TProjection>( IStreamAggregator <TProjection, TData> aggregator, FetchAndSaveProjection <TProjection> fetchAndSaveProjection, HandleAggregatorError <TProjection> onError) { var added = aggregatorSubscriptions.TryAdd(typeof(TProjection), new AggregatorSubscription <TProjection, TData>(aggregator, fetchAndSaveProjection, onError)); if (!added) { throw new InvalidOperationException(string.Format("Aggregator for projection of type {0} is already subscribed.", typeof(TProjection))); } return(Disposable.Create(() => { IAggregatorSubscription _; aggregatorSubscriptions.TryRemove(typeof(TProjection), out _); })); }
public DistributedStreamCatchup( IPartitionedStream <TData, TCursor, TPartition> partitionedStream, IEnumerable <IStreamQueryPartition <TPartition> > partitions, int?batchSize = null, FetchAndSaveProjection <ICursor <TCursor> > manageCursor = null, IDistributor <IStreamQueryPartition <TPartition> > distributor = null) : base(batchSize) { if (partitionedStream == null) { throw new ArgumentNullException("partitionedStream"); } if (partitions == null) { throw new ArgumentNullException("partitions"); } this.partitionedStream = partitionedStream; this.distributor = distributor ?? partitions.DistributeQueriesInProcess(); manageCursor = manageCursor ?? ((id, aggregate) => aggregate(null)); this.distributor #if DEBUG .Trace() // TODO: (DistributedStreamCatchup) figure out a way to let people Trace this distributor #endif .OnReceive(async lease => { await manageCursor(lease.ResourceName, async cursor => { var catchup = new SingleStreamCatchup <TData, TCursor>( await partitionedStream.GetStream(lease.Resource), initialCursor: cursor, batchSize: batchSize, subscriptions: new ConcurrentDictionary <Type, IAggregatorSubscription>(aggregatorSubscriptions)); return(await catchup.RunSingleBatch()); }); }); }
/// <summary> /// Initializes a new instance of the <see cref="MultiStreamCatchup{TData, TUpstreamCursor, TDownstreamCursor}"/> class. /// </summary> /// <param name="upstreamCatchup">The upstream catchup.</param> /// <exception cref="ArgumentNullException"> /// upstreamCatchup /// or /// manageCursor /// </exception> public MultiStreamCatchup( IStreamCatchup <IStream <TData, TDownstreamCursor>, TUpstreamCursor> upstreamCatchup, FetchAndSaveProjection <ICursor <TUpstreamCursor> > manageCursor) { if (upstreamCatchup == null) { throw new ArgumentNullException("upstreamCatchup"); } if (manageCursor == null) { throw new ArgumentNullException("manageCursor"); } this.upstreamCatchup = upstreamCatchup; upstreamCatchup.Subscribe( async(cursor, streams) => { // ths upstream cursor is not passed here because the downstream streams have their own independent cursors await Task.WhenAll(streams.Select(stream => RunSingleBatch(stream))); return(cursor); }, manageCursor); }
public AggregatorSubscription( IStreamAggregator <TProjection, TData> aggregator, FetchAndSaveProjection <TProjection> fetchAndSaveProjection = null, HandleAggregatorError <TProjection> onError = null) { if (aggregator == null) { throw new ArgumentNullException("aggregator"); } OnError = onError ?? (error => { }); if (onError != null) { fetchAndSaveProjection = fetchAndSaveProjection.Catch(onError); } FetchAndSaveProjection = fetchAndSaveProjection ?? (async(streamId, aggregate) => { await aggregate(Activator.CreateInstance <TProjection>()); }); Aggregator = aggregator; }