Browse Source

Snapshot history.

pull/306/head
Sebastian 8 years ago
parent
commit
ecfafdc702
  1. 7
      src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
  2. 6
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  3. 4
      src/Squidex.Domain.Apps.Entities/Contents/IContentGrain.cs
  4. 7
      src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs
  5. 7
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs
  6. 60
      src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs
  7. 67
      tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs

7
src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs

@ -57,6 +57,13 @@ namespace Squidex.Domain.Apps.Entities.Apps
this.initialPatterns = initialPatterns;
}
public override Task OnActivateAsync()
{
CleanupOldSnapshots();
return base.OnActivateAsync();
}
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
VerifyNotArchived();

6
src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs

@ -20,6 +20,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
@ -289,5 +290,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
return operationContext;
}
public Task<J<IContentEntity>> GetStateAsync(long version = -2)
{
return Task.FromResult(J.Of<IContentEntity>(GetSnapshot(version)));
}
}
}

4
src/Squidex.Domain.Apps.Entities/Contents/IContentGrain.cs

@ -5,11 +5,15 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Contents
{
public interface IContentGrain : IDomainObjectGrain
{
Task<J<IContentEntity>> GetStateAsync(long version = EtagVersion.Any);
}
}

7
src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs

@ -34,6 +34,13 @@ namespace Squidex.Domain.Apps.Entities.Rules
this.appProvider = appProvider;
}
public override Task OnActivateAsync()
{
CleanupOldSnapshots();
return base.OnActivateAsync();
}
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
VerifyNotDeleted();

7
src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs

@ -40,6 +40,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas
this.registry = registry;
}
public override Task OnActivateAsync()
{
CleanupOldSnapshots();
return base.OnActivateAsync();
}
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
VerifyNotDeleted();

60
src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs

@ -21,8 +21,9 @@ namespace Squidex.Infrastructure.Commands
private readonly List<Envelope<IEvent>> uncomittedEvents = new List<Envelope<IEvent>>();
private readonly IStore<Guid> store;
private readonly ISemanticLog log;
private readonly List<T> snapshots = new List<T> { new T { Version = EtagVersion.Empty } };
private bool cleanup;
private Guid id;
private T snapshot = new T { Version = EtagVersion.Empty };
private IPersistence<T> persistence;
public Guid Id
@ -32,17 +33,17 @@ namespace Squidex.Infrastructure.Commands
public long Version
{
get { return snapshot.Version; }
get { return snapshots.Count - 2; }
}
public long NewVersion
{
get { return snapshot.Version + uncomittedEvents.Count; }
get { return Version; }
}
public T Snapshot
{
get { return snapshot; }
get { return snapshots[snapshots.Count - 1]; }
}
protected DomainObjectGrain(IStore<Guid> store, ISemanticLog log)
@ -55,6 +56,31 @@ namespace Squidex.Infrastructure.Commands
this.log = log;
}
public void CleanupOldSnapshots()
{
cleanup = true;
}
public T GetSnapshot(long version)
{
if (version == EtagVersion.Any)
{
return Snapshot;
}
if (version == EtagVersion.Empty)
{
return snapshots[0];
}
if (version >= 0 && version < snapshots.Count - 1)
{
return snapshots[(int)version + 1];
}
return default(T);
}
public override async Task OnActivateAsync(Guid key)
{
using (log.MeasureInformation(w => w
@ -96,9 +122,11 @@ namespace Squidex.Infrastructure.Commands
uncomittedEvents.Clear();
}
public virtual void ApplySnapshot(T newSnapshot)
public virtual void ApplySnapshot(T snapshot)
{
snapshot = newSnapshot;
snapshot.Version = snapshots.Count - 1;
snapshots.Add(snapshot);
}
public virtual void ApplyEvent(Envelope<IEvent> @event)
@ -172,7 +200,8 @@ namespace Squidex.Infrastructure.Commands
throw new DomainException("Object has already been created.");
}
var previousSnapshot = snapshot;
var size = snapshots.Count;
try
{
var result = await handler(command);
@ -181,10 +210,8 @@ namespace Squidex.Infrastructure.Commands
if (events.Length > 0)
{
snapshot.Version = NewVersion;
await persistence.WriteEventsAsync(events);
await persistence.WriteSnapshotAsync(snapshot);
await persistence.WriteSnapshotAsync(Snapshot);
}
if (result == null)
@ -203,12 +230,23 @@ namespace Squidex.Infrastructure.Commands
}
catch
{
snapshot = previousSnapshot;
while (snapshots.Count > size)
{
snapshots.RemoveAt(snapshots.Count - 1);
}
throw;
}
finally
{
if (cleanup)
{
for (var i = 0; i < snapshots.Count - 1; i++)
{
snapshots[i] = default(T);
}
}
uncomittedEvents.Clear();
}
}

67
tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
using FluentAssertions;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
@ -124,6 +125,72 @@ namespace Squidex.Infrastructure.Commands
Assert.Equal(EtagVersion.Empty, sut.Version);
}
[Fact]
public async Task Should_get_latestet_version_when_requesting_state_with_any()
{
await SetupEmptyAsync();
await sut.ExecuteAsync(C(new CreateAuto { Value = 5 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 10 }));
var result = sut.GetSnapshot(EtagVersion.Any);
result.Should().BeEquivalentTo(new MyDomainState { Value = 10, Version = 1 });
}
[Fact]
public async Task Should_get_empty_version_when_requesting_state_with_empty_version()
{
await SetupEmptyAsync();
await sut.ExecuteAsync(C(new CreateAuto { Value = 5 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 10 }));
var result = sut.GetSnapshot(EtagVersion.Empty);
result.Should().BeEquivalentTo(new MyDomainState { Value = 0, Version = -1 });
}
[Fact]
public async Task Should_get_specific_version_when_requesting_state_with_specific_version()
{
await SetupEmptyAsync();
await sut.ExecuteAsync(C(new CreateAuto { Value = 5 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 10 }));
sut.GetSnapshot(0).Should().BeEquivalentTo(new MyDomainState { Value = 5, Version = 0 });
sut.GetSnapshot(1).Should().BeEquivalentTo(new MyDomainState { Value = 10, Version = 1 });
}
[Fact]
public async Task Should_automatically_cleanup_old_snapshots_when_enabled()
{
await SetupEmptyAsync();
sut.CleanupOldSnapshots();
await sut.ExecuteAsync(C(new CreateAuto { Value = 5 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 10 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 15 }));
Assert.Null(sut.GetSnapshot(0));
Assert.Null(sut.GetSnapshot(1));
Assert.NotNull(sut.GetSnapshot(2));
}
[Fact]
public async Task Should_get_null_state_when_requesting_state_with_invalid_version()
{
await SetupEmptyAsync();
await sut.ExecuteAsync(C(new CreateAuto { Value = 5 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 10 }));
Assert.Null(sut.GetSnapshot(-3));
Assert.Null(sut.GetSnapshot(2));
}
[Fact]
public async Task Should_write_state_and_events_when_created()
{

Loading…
Cancel
Save