public async Task empty_snapshot_is_not_persisted() { var nullSnapshot = new SnapshotInfo("empty", 0, null, string.Empty); await _snapshots.AddAsync("empty", nullSnapshot); var tape = new Recorder(); await Store.ReadForwardAsync("empty", 0, tape); Assert.True(tape.IsEmpty); }
public async Task should_load_snapshots() { await _snapshots.AddAsync("sequence_1/Sum", new SnapshotInfo( "sequence_1", 9, new Sum { Total = 45 }, "1" )).ConfigureAwait(false); var sequence = await CreateStream("sequence_1").ConfigureAwait(false); var result = await sequence .Fold() .WithCache(_snapshots) .RunAsync <Sum>().ConfigureAwait(false); Assert.Equal(45 + 10, result.Total); }
public static Task AddAsync( this ISnapshotStore snapshots, string snapshotPartitionId, SnapshotInfo snapshot ) { return(snapshots.AddAsync(snapshotPartitionId, snapshot, CancellationToken.None)); }
public async Task SaveAsync <T>( T aggregate, string operationId, Action <IHeadersAccessor> headers, CancellationToken cancellationToken ) where T : IAggregate { var persister = (IEventSourcedAggregate)aggregate; var changeSet = persister.GetChangeSet(); if (changeSet.IsEmpty() && !PersistEmptyChangeset) { return; } var stream = GetStream(aggregate); if (!stream.IsWritable) { throw new AggregateReadOnlyException(); } if (aggregate is IInvariantsChecker checker) { var check = checker.CheckInvariants(); if (check.IsInvalid) { // checks for idempotency if (await stream.ContainsOperationAsync(operationId).ConfigureAwait(false)) { // already stored, we just need to skip and avoid exception return; } } check.ThrowIfInvalid(); } headers?.Invoke(changeSet); var chunk = await stream.AppendAsync(changeSet, operationId, cancellationToken).ConfigureAwait(false); //remember to check if the chunk was really persisted or skipped because of operationId idempotency. if (chunk != null) { persister.Persisted(changeSet); if (_snapshots != null && aggregate is ISnaphottable snaphottable) { //we need to await, it's responsibility of the snapshot provider to clone & store state (sync or async) await _snapshots.AddAsync(aggregate.Id, snaphottable.GetSnapshot(), cancellationToken).ConfigureAwait(false); } } else { Clear(); //persister.ClearUncommitted(); } }
public async Task <TResult> RunAsync <TResult>(IPayloadProcessor processor, CancellationToken cancellationToken) where TResult : new() { long startIndex = 1; TResult state = default(TResult); string snapshotId = this._source.Id + "/" + typeof(TResult).Name; if (_snapshots != null) { var si = await LoadSnapshot(snapshotId, cancellationToken).ConfigureAwait(false); if (si != null) { state = (TResult)si.Payload; if (_upToIndex.HasValue && si.SourceVersion == _upToIndex.Value) { return(state); } startIndex = si.SourceVersion + 1; } } if (state == null) { state = new TResult(); } var reducer = new Reducer <TResult>(state, _onMissing, processor); await _source.ReadAsync(reducer, startIndex, _upToIndex ?? long.MaxValue, cancellationToken) .ConfigureAwait(false); if (_snapshots != null && reducer.LastIndex > 0) { await _snapshots.AddAsync(snapshotId, new SnapshotInfo( _source.Id, reducer.LastIndex, state, "1" )).ConfigureAwait(false); } return(state); }
public async Task Save <T>( T aggregate, string operationId, Action <IHeadersAccessor> headers, CancellationToken cancellationToken ) where T : IAggregate { var persister = (IEventSourcedAggregate)aggregate; var changeSet = persister.GetChangeSet(); if (changeSet.IsEmpty() && !PersistEmptyChangeset) { return; } if (aggregate is IInvariantsChecker checker) { var check = checker.CheckInvariants(); check.ThrowIfInvalid(); } var stream = GetStream(aggregate); if (!stream.IsWritable) { throw new AggregateReadOnlyException(); } headers?.Invoke(changeSet); await stream.AppendAsync(changeSet, operationId, cancellationToken).ConfigureAwait(false); persister.Persisted(changeSet); if (_snapshots != null && aggregate is ISnaphottable snaphottable) { //we need to await, it's responsibility of the snapshot provider to clone & store state (sync or async) await _snapshots.AddAsync(aggregate.Id, snaphottable.GetSnapshot(), cancellationToken).ConfigureAwait(false); } }