// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using System.Collections.Generic; using System.Threading.Tasks; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Tasks; namespace Squidex.Infrastructure.Commands { public abstract class DomainObjectGrainBase : GrainOfGuid, IDomainObjectGrain where T : IDomainState, new() { private readonly List> uncomittedEvents = new List>(); private readonly ISemanticLog log; private Guid id; private enum Mode { Create, Update, Upsert } public Guid Id { get { return id; } } public long Version { get { return Snapshot.Version; } } public abstract T Snapshot { get; } protected DomainObjectGrainBase(ISemanticLog log) { Guard.NotNull(log); this.log = log; } protected override async Task OnActivateAsync(Guid key) { var logContext = (key: key.ToString(), name: GetType().Name); using (log.MeasureInformation(logContext, (ctx, w) => w .WriteProperty("action", "ActivateDomainObject") .WriteProperty("domainObjectType", ctx.name) .WriteProperty("domainObjectKey", ctx.key))) { id = key; await ReadAsync(GetType(), id); } } public void RaiseEvent(IEvent @event) { RaiseEvent(Envelope.Create(@event)); } public virtual void RaiseEvent(Envelope @event) { Guard.NotNull(@event); @event.SetAggregateId(id); ApplyEvent(@event); uncomittedEvents.Add(@event); } public IReadOnlyList> GetUncomittedEvents() { return uncomittedEvents; } public void ClearUncommittedEvents() { uncomittedEvents.Clear(); } protected Task CreateReturnAsync(TCommand command, Func> handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler, Mode.Create); } protected Task CreateReturn(TCommand command, Func handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler?.ToAsync()!, Mode.Create); } protected Task CreateAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler.ToDefault(), Mode.Create); } protected Task Create(TCommand command, Action handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler?.ToDefault()?.ToAsync()!, Mode.Create); } protected Task UpdateReturnAsync(TCommand command, Func> handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler, Mode.Update); } protected Task UpdateReturn(TCommand command, Func handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler?.ToAsync()!, Mode.Update); } protected Task UpdateAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler?.ToDefault()!, Mode.Update); } protected Task Update(TCommand command, Action handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler?.ToDefault()?.ToAsync()!, Mode.Update); } protected Task UpsertReturnAsync(TCommand command, Func> handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler, Mode.Upsert); } protected Task UpsertReturn(TCommand command, Func handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler?.ToAsync()!, Mode.Upsert); } protected Task UpsertAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler?.ToDefault()!, Mode.Upsert); } protected Task Upsert(TCommand command, Action handler) where TCommand : class, IAggregateCommand { return InvokeAsync(command, handler?.ToDefault()?.ToAsync()!, Mode.Upsert); } private async Task InvokeAsync(TCommand command, Func> handler, Mode mode) where TCommand : class, IAggregateCommand { Guard.NotNull(command); Guard.NotNull(handler); if (command.ExpectedVersion > EtagVersion.Any && command.ExpectedVersion != Version) { throw new DomainObjectVersionException(id.ToString(), GetType(), Version, command.ExpectedVersion); } if (mode == Mode.Update && Version < 0) { throw new DomainObjectNotFoundException(id.ToString(), GetType()); } if (mode == Mode.Create && Version >= 0) { throw new DomainException("Object has already been created."); } var previousSnapshot = Snapshot; var previousVersion = Version; try { var result = await handler(command); var events = uncomittedEvents.ToArray(); await WriteAsync(events, previousVersion); if (result == null) { if (mode == Mode.Update || (mode == Mode.Upsert && Version == 0)) { result = new EntitySavedResult(Version); } else { result = EntityCreatedResult.Create(id, Version); } } return result; } catch { RestorePreviousSnapshot(previousSnapshot, previousVersion); throw; } finally { ClearUncommittedEvents(); } } protected abstract void RestorePreviousSnapshot(T previousSnapshot, long previousVersion); protected abstract void ApplyEvent(Envelope @event); protected abstract Task ReadAsync(Type type, Guid id); protected abstract Task WriteAsync(Envelope[] newEvents, long previousVersion); public async Task> ExecuteAsync(J command) { var result = await ExecuteAsync(command.Value); return result; } protected abstract Task ExecuteAsync(IAggregateCommand command); } }