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; this.initialPatterns = initialPatterns;
} }
public override Task OnActivateAsync()
{
CleanupOldSnapshots();
return base.OnActivateAsync();
}
protected override Task<object> ExecuteAsync(IAggregateCommand command) protected override Task<object> ExecuteAsync(IAggregateCommand command)
{ {
VerifyNotArchived(); VerifyNotArchived();

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

@ -20,6 +20,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -289,5 +290,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
return operationContext; 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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public interface IContentGrain : IDomainObjectGrain 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; this.appProvider = appProvider;
} }
public override Task OnActivateAsync()
{
CleanupOldSnapshots();
return base.OnActivateAsync();
}
protected override Task<object> ExecuteAsync(IAggregateCommand command) protected override Task<object> ExecuteAsync(IAggregateCommand command)
{ {
VerifyNotDeleted(); VerifyNotDeleted();

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

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

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

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using FluentAssertions;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
@ -124,6 +125,72 @@ namespace Squidex.Infrastructure.Commands
Assert.Equal(EtagVersion.Empty, sut.Version); 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] [Fact]
public async Task Should_write_state_and_events_when_created() public async Task Should_write_state_and_events_when_created()
{ {

Loading…
Cancel
Save