/// <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;
                }
            }
        }
Exemplo n.º 4
0
 /// <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;
            }
        }