|
|
@ -21,15 +21,17 @@ namespace Squidex.Infrastructure.States |
|
|
private readonly IStreamNameResolver streamNameResolver; |
|
|
private readonly IStreamNameResolver streamNameResolver; |
|
|
private readonly IEventStore eventStore; |
|
|
private readonly IEventStore eventStore; |
|
|
private readonly IEventDataFormatter eventDataFormatter; |
|
|
private readonly IEventDataFormatter eventDataFormatter; |
|
|
|
|
|
private readonly PersistenceMode persistenceMode; |
|
|
private readonly Action invalidate; |
|
|
private readonly Action invalidate; |
|
|
private readonly Func<TState, Task> applyState; |
|
|
private readonly Func<TState, Task> applyState; |
|
|
private readonly Func<Envelope<IEvent>, Task> applyEvent; |
|
|
private readonly Func<Envelope<IEvent>, Task> applyEvent; |
|
|
private long positionSnapshot = -1; |
|
|
private long versionSnapshot = -1; |
|
|
private long positionEvent = -1; |
|
|
private long versionEvents = -1; |
|
|
|
|
|
private long version; |
|
|
|
|
|
|
|
|
public long Version |
|
|
public long Version |
|
|
{ |
|
|
{ |
|
|
get { return Math.Max(positionEvent, positionSnapshot); } |
|
|
get { return version; } |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public Persistence(string ownerKey, |
|
|
public Persistence(string ownerKey, |
|
|
@ -38,6 +40,7 @@ namespace Squidex.Infrastructure.States |
|
|
IEventDataFormatter eventDataFormatter, |
|
|
IEventDataFormatter eventDataFormatter, |
|
|
ISnapshotStore<TState> snapshotStore, |
|
|
ISnapshotStore<TState> snapshotStore, |
|
|
IStreamNameResolver streamNameResolver, |
|
|
IStreamNameResolver streamNameResolver, |
|
|
|
|
|
PersistenceMode persistenceMode, |
|
|
Func<TState, Task> applyState, |
|
|
Func<TState, Task> applyState, |
|
|
Func<Envelope<IEvent>, Task> applyEvent) |
|
|
Func<Envelope<IEvent>, Task> applyEvent) |
|
|
{ |
|
|
{ |
|
|
@ -47,37 +50,61 @@ namespace Squidex.Infrastructure.States |
|
|
this.invalidate = invalidate; |
|
|
this.invalidate = invalidate; |
|
|
this.eventStore = eventStore; |
|
|
this.eventStore = eventStore; |
|
|
this.eventDataFormatter = eventDataFormatter; |
|
|
this.eventDataFormatter = eventDataFormatter; |
|
|
|
|
|
this.persistenceMode = persistenceMode; |
|
|
this.snapshotStore = snapshotStore; |
|
|
this.snapshotStore = snapshotStore; |
|
|
this.streamNameResolver = streamNameResolver; |
|
|
this.streamNameResolver = streamNameResolver; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async Task ReadAsync(long? expectedVersion) |
|
|
public async Task ReadAsync(long expectedVersion = ExpectedVersion.Any) |
|
|
{ |
|
|
{ |
|
|
positionSnapshot = -1; |
|
|
versionSnapshot = -1; |
|
|
positionEvent = -1; |
|
|
versionEvents = -1; |
|
|
|
|
|
|
|
|
if (applyState != null) |
|
|
await ReadSnapshotAsync(); |
|
|
|
|
|
await ReadEventsAsync(); |
|
|
|
|
|
|
|
|
|
|
|
UpdateVersion(); |
|
|
|
|
|
|
|
|
|
|
|
if (expectedVersion != ExpectedVersion.Any && expectedVersion != version) |
|
|
|
|
|
{ |
|
|
|
|
|
if (version == ExpectedVersion.Empty) |
|
|
|
|
|
{ |
|
|
|
|
|
throw new DomainObjectNotFoundException(ownerKey, typeof(TOwner)); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
throw new DomainObjectVersionException(ownerKey, typeof(TOwner), version, expectedVersion); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async Task ReadSnapshotAsync() |
|
|
|
|
|
{ |
|
|
|
|
|
if (UseSnapshots()) |
|
|
{ |
|
|
{ |
|
|
var (state, position) = await snapshotStore.ReadAsync(ownerKey); |
|
|
var (state, position) = await snapshotStore.ReadAsync(ownerKey); |
|
|
|
|
|
|
|
|
positionSnapshot = position; |
|
|
versionSnapshot = position; |
|
|
positionEvent = position; |
|
|
versionEvents = position; |
|
|
|
|
|
|
|
|
if (applyState != null && position >= 0) |
|
|
if (applyState != null && position >= 0) |
|
|
{ |
|
|
{ |
|
|
await applyState(state); |
|
|
await applyState(state); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (applyEvent != null && streamNameResolver != null) |
|
|
private async Task ReadEventsAsync() |
|
|
|
|
|
{ |
|
|
|
|
|
if (UseEventSourcing()) |
|
|
{ |
|
|
{ |
|
|
var events = await eventStore.GetEventsAsync(GetStreamName(), positionEvent + 1); |
|
|
var events = await eventStore.GetEventsAsync(GetStreamName(), versionEvents + 1); |
|
|
|
|
|
|
|
|
foreach (var @event in events) |
|
|
foreach (var @event in events) |
|
|
{ |
|
|
{ |
|
|
positionEvent++; |
|
|
versionEvents++; |
|
|
|
|
|
|
|
|
if (@event.EventStreamNumber != positionEvent) |
|
|
if (@event.EventStreamNumber != versionEvents) |
|
|
{ |
|
|
{ |
|
|
throw new InvalidOperationException("Events must follow the snapshot version in consecutive order with no gaps."); |
|
|
throw new InvalidOperationException("Events must follow the snapshot version in consecutive order with no gaps."); |
|
|
} |
|
|
} |
|
|
@ -90,46 +117,28 @@ namespace Squidex.Infrastructure.States |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var newVersion = Version; |
|
|
|
|
|
|
|
|
|
|
|
if (expectedVersion.HasValue && expectedVersion.Value != newVersion) |
|
|
|
|
|
{ |
|
|
|
|
|
if (newVersion == -1) |
|
|
|
|
|
{ |
|
|
|
|
|
throw new DomainObjectNotFoundException(ownerKey, typeof(TOwner)); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
throw new DomainObjectVersionException(ownerKey, typeof(TOwner), newVersion, expectedVersion.Value); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async Task WriteSnapshotAsync(TState state, long newVersion = -1) |
|
|
public async Task WriteSnapshotAsync(TState state) |
|
|
{ |
|
|
{ |
|
|
if (newVersion < 0) |
|
|
var newVersion = UseEventSourcing() ? versionEvents : versionSnapshot + 1; |
|
|
{ |
|
|
|
|
|
newVersion = |
|
|
|
|
|
applyEvent != null ? |
|
|
|
|
|
positionEvent : |
|
|
|
|
|
positionSnapshot + 1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (newVersion != positionSnapshot) |
|
|
if (newVersion != versionSnapshot) |
|
|
{ |
|
|
{ |
|
|
try |
|
|
try |
|
|
{ |
|
|
{ |
|
|
await snapshotStore.WriteAsync(ownerKey, state, positionSnapshot, newVersion); |
|
|
await snapshotStore.WriteAsync(ownerKey, state, versionSnapshot, newVersion); |
|
|
} |
|
|
} |
|
|
catch (InconsistentStateException ex) |
|
|
catch (InconsistentStateException ex) |
|
|
{ |
|
|
{ |
|
|
throw new DomainObjectVersionException(ownerKey, typeof(TOwner), ex.CurrentVersion, ex.ExpectedVersion); |
|
|
throw new DomainObjectVersionException(ownerKey, typeof(TOwner), ex.CurrentVersion, ex.ExpectedVersion); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
positionSnapshot = newVersion; |
|
|
versionSnapshot = newVersion; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
UpdateVersion(); |
|
|
|
|
|
|
|
|
invalidate?.Invoke(); |
|
|
invalidate?.Invoke(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -141,6 +150,8 @@ namespace Squidex.Infrastructure.States |
|
|
|
|
|
|
|
|
if (eventArray.Length > 0) |
|
|
if (eventArray.Length > 0) |
|
|
{ |
|
|
{ |
|
|
|
|
|
var expectedVersion = UseEventSourcing() ? version : ExpectedVersion.Any; |
|
|
|
|
|
|
|
|
var commitId = Guid.NewGuid(); |
|
|
var commitId = Guid.NewGuid(); |
|
|
|
|
|
|
|
|
var eventStream = GetStreamName(); |
|
|
var eventStream = GetStreamName(); |
|
|
@ -155,9 +166,11 @@ namespace Squidex.Infrastructure.States |
|
|
throw new DomainObjectVersionException(ownerKey, typeof(TOwner), ex.CurrentVersion, ex.ExpectedVersion); |
|
|
throw new DomainObjectVersionException(ownerKey, typeof(TOwner), ex.CurrentVersion, ex.ExpectedVersion); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
positionEvent += eventArray.Length; |
|
|
versionEvents += eventArray.Length; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
UpdateVersion(); |
|
|
|
|
|
|
|
|
invalidate?.Invoke(); |
|
|
invalidate?.Invoke(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -171,6 +184,16 @@ namespace Squidex.Infrastructure.States |
|
|
return streamNameResolver.GetStreamName(typeof(TOwner), ownerKey); |
|
|
return streamNameResolver.GetStreamName(typeof(TOwner), ownerKey); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private bool UseSnapshots() |
|
|
|
|
|
{ |
|
|
|
|
|
return persistenceMode == PersistenceMode.Snapshots || persistenceMode == PersistenceMode.SnapshotsAndEventSourcing; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private bool UseEventSourcing() |
|
|
|
|
|
{ |
|
|
|
|
|
return persistenceMode == PersistenceMode.EventSourcing || persistenceMode == PersistenceMode.SnapshotsAndEventSourcing; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private Envelope<IEvent> ParseKnownEvent(StoredEvent storedEvent) |
|
|
private Envelope<IEvent> ParseKnownEvent(StoredEvent storedEvent) |
|
|
{ |
|
|
{ |
|
|
try |
|
|
try |
|
|
@ -182,5 +205,21 @@ namespace Squidex.Infrastructure.States |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void UpdateVersion() |
|
|
|
|
|
{ |
|
|
|
|
|
if (persistenceMode == PersistenceMode.Snapshots) |
|
|
|
|
|
{ |
|
|
|
|
|
version = versionSnapshot; |
|
|
|
|
|
} |
|
|
|
|
|
else if (persistenceMode == PersistenceMode.EventSourcing) |
|
|
|
|
|
{ |
|
|
|
|
|
version = versionEvents; |
|
|
|
|
|
} |
|
|
|
|
|
else if (persistenceMode == PersistenceMode.SnapshotsAndEventSourcing) |
|
|
|
|
|
{ |
|
|
|
|
|
version = Math.Max(versionEvents, versionSnapshot); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|