/// <summary> /// Initializes a new instance of <see cref="CommandContext"/> with the specified <paramref name="commandId"/> and <paramref name="headers"/>. /// </summary> /// <param name="commandId">The unique <see cref="Command"/> identifier.</param> /// <param name="headers">The <see cref="Command"/> headers.</param> /// <param name="envelope">The <see cref="CommandEnvelope"/> associated with this context.</param> public CommandContext(Guid commandId, HeaderCollection headers, CommandEnvelope envelope) { Verify.NotNull(headers, nameof(headers)); Verify.NotNull(envelope, nameof(envelope)); this.raisedEvents = new List<Event>(); this.originalContext = currentContext; this.thread = Thread.CurrentThread; this.commandId = commandId; this.envelope = envelope; this.headers = headers; currentContext = this; }
/// <summary> /// Invokes the underlying <see cref="Aggregate"/> command handler method for <see cref="Command"/>. /// </summary> /// <param name="context">The current command context.</param> public void Handle(CommandContext context) { var command = context.Command; var aggregate = aggregateStore.Get(AggregateType, context.AggregateId); Log.Trace("Executing {0} command on aggregate {1}", command, aggregate); aggregate.VerifyCanHandleCommand(command); executor(aggregate, command); if (context.HasRaisedEvents) { aggregateStore.Save(aggregate, context); } else { Log.Warn("Executing {0} command on aggregate {1} raised no events", command, aggregate); } }
/// <summary> /// Save the specified <paramref name="context"/> changes for the given aggregate. /// </summary> /// <param name="aggregate">The current aggregate version for which the context applies.</param> /// <param name="context">The command context containing the aggregate changes to be applied.</param> public SaveResult Save(Aggregate aggregate, CommandContext context) { Verify.NotNull(aggregate, nameof(aggregate)); Verify.NotNull(context, nameof(context)); var copy = aggregate.Copy(); var aggregateType = aggregate.GetType(); var key = String.Concat(aggregateType.GetFullNameWithAssembly(), "-", aggregate.Id); using (var aggregateLock = new AggregateLock(aggregateType, aggregate.Id)) { aggregateLock.Aquire(); try { var result = aggregateStore.Save(copy, context); memoryCache.Set(key, copy, CreateCacheItemPolicy()); return result; } catch (ConcurrencyException) { memoryCache.Remove(key); throw; } } }
/// <summary> /// When overriden, defines the custom behavior to be invoked prior to saving the specified <paramref name="aggregate"/>. /// </summary> /// <param name="aggregate">The aggregate to be modified by the current <paramref name="context"/>.</param> /// <param name="context">The current <see cref="CommandContext"/> containing the pending aggregate modifications.</param> public virtual void PreSave(Aggregate aggregate, CommandContext context) { }
/// <summary> /// Releases all managed resources used by the current instance of the <see cref="CommandContext"/> class. /// </summary> public void Dispose() { if (disposed) return; if (thread != Thread.CurrentThread) throw new InvalidOperationException(Exceptions.CommandContextInterleaved); if (this != Current) throw new InvalidOperationException(Exceptions.CommandContextInvalidThread); disposed = true; currentContext = originalContext; }
/// <summary> /// Creates a commit for the specified <paramref name="aggregate"/> based on the provided <paramref name="context"/>. /// </summary> /// <param name="aggregate">The <see cref="Aggregate"/> instance for which the commit is to be applied.</param> /// <param name="context">The <see cref="CommandContext"/> instance containing the pending modifications to the associated <paramref name="aggregate"/>.</param> private static Commit CreateCommit(Aggregate aggregate, CommandContext context) { EventCollection events = context.GetRaisedEvents(); HeaderCollection headers; if (aggregate.Version == 0) { var typeHeader = new Header(Header.Aggregate, aggregate.GetType().GetFullNameWithAssembly(), checkReservedNames: false); headers = new HeaderCollection(context.Headers.Concat(typeHeader)); } else { headers = context.Headers; } return new Commit(context.CommandId, aggregate.Id, aggregate.Version + 1, headers, events); }
/// <summary> /// Save the specified <paramref name="context"/> changes for the given aggregate. /// </summary> /// <param name="aggregate">The current aggregate version for which the context applies.</param> /// <param name="context">The command context containing the aggregate changes to be applied.</param> public SaveResult Save(Aggregate aggregate, CommandContext context) { Verify.NotNull(aggregate, nameof(aggregate)); Verify.NotNull(context, nameof(context)); var commit = CreateCommit(aggregate, context); try { eventStore.Save(commit); } catch (DuplicateCommitException) { Log.Warn("Duplicate commit: {0}", commit); } // NOTE: Apply commit directly to existing aggregate. By default, each call to `Get` returns a new `Aggregate` instance. // Should the caller hold on to a reference to `this` aggregate instance, it is their responsibility to gaurd against // modifications (via `aggregate.Copy()` call) if they require an unaltered instance of `aggregate`. ApplyCommitToAggregate(commit, aggregate); if (aggregate.Version > 0 && aggregate.Version % snapshotInterval == 0) snapshotStore.Save(new Snapshot(aggregate.Id, aggregate.Version, aggregate)); return new SaveResult(aggregate, commit); }
/// <summary> /// Verify that the specified <paramref name="aggregate"/> state has not been corrupted before proceeding with the save. /// </summary> /// <param name="aggregate">The aggregate to be modified by the current <paramref name="context"/>.</param> /// <param name="context">The current <see cref="CommandContext"/> containing the pending aggregate modifications.</param> public override void PreSave(Aggregate aggregate, CommandContext context) { aggregate.VerifyHash(); }
/// <summary> /// Invokes zero or more customized <see cref="PipelineHook.PreSave"/> implementations. /// </summary> /// <param name="aggregate">The aggregate to be modified by the current <paramref name="context"/>.</param> /// <param name="context">The current <see cref="CommandContext"/> containing the pending aggregate modifications.</param> private void InvokePreSaveHooks(Aggregate aggregate, CommandContext context) { foreach (var pipelineHook in preSaveHooks) { Log.Trace("Invoking pre-save pipeline hook: {0}", pipelineHook); pipelineHook.PreSave(aggregate, context); } }
/// <summary> /// Save the specified <paramref name="context"/> changes for the given aggregate. /// </summary> /// <param name="aggregate">The current aggregate version for which the context applies.</param> /// <param name="context">The command context containing the aggregate changes to be applied.</param> public SaveResult Save(Aggregate aggregate, CommandContext context) { InvokePreSaveHooks(aggregate, context); try { var result = aggregateStore.Save(aggregate, context); InvokePostSaveHooks(result.Aggregate, result.Commit, null); return result; } catch (Exception ex) { InvokePostSaveHooks(aggregate, null, ex); throw; } }