/// <summary> /// When overriden, defines the custom behavior to be invoked after attempting to save the specified <paramref name="aggregate"/>. /// </summary> /// <param name="aggregate">The modified <see cref="Aggregate"/> instance if <paramref name="commit"/> is not <value>null</value>; otherwise the original <see cref="Aggregate"/> instance if <paramref name="error"/> is not <value>null</value>.</param> /// <param name="commit">The <see cref="Commit"/> generated if the save was successful; otherwise <value>null</value>.</param> /// <param name="error">The <see cref="Exception"/> thrown if the save was unsuccessful; otherwise <value>null</value>.</param> public override void PostSave(Aggregate aggregate, Commit commit, Exception error) { base.PostSave(aggregate, commit, error); if (error is ConcurrencyException) statistics.IncrementConflictCount(); }
/// <summary> /// Initializes a new instance of <see cref="SaveResult"/>. /// </summary> /// <param name="aggregate">The saved <see cref="Aggregate"/>.</param> /// <param name="commit">The generated <see cref="Commit"/></param> public SaveResult(Aggregate aggregate, Commit commit) { Verify.NotNull(aggregate, nameof(aggregate)); Verify.NotNull(commit, nameof(commit)); Aggregate = aggregate; Commit = commit; }
/// <summary> /// Verify that the specified <paramref name="aggregate"/> state has not been corrupted after a failed save attempt and update the check sum if the save was successful. /// </summary> /// <param name="aggregate">The modified <see cref="Aggregate"/> instance if <paramref name="commit"/> is not <value>null</value>; otherwise the original <see cref="Aggregate"/> instance if <paramref name="error"/> is not <value>null</value>.</param> /// <param name="commit">The <see cref="Commit"/> generated if the save was successful; otherwise <value>null</value>.</param> /// <param name="error">The <see cref="Exception"/> thrown if the save was unsuccessful; otherwise <value>null</value>.</param> public override void PostSave(Aggregate aggregate, Commit commit, Exception error) { if (error != null) aggregate.VerifyHash(); if (commit != null) aggregate.UpdateHash(); }
/// <summary> /// Apply the specified <see cref="Event"/> <paramref name="e"/> to the provided <paramref name="aggregate"/>. /// </summary> /// <param name="e">The event to be applied to the provided <paramref name="aggregate"/>.</param> /// <param name="aggregate">The <see cref="Aggregate"/> instance on which the event is to be applied.</param> public void Apply(Event e, Aggregate aggregate) { Verify.NotNull(e, nameof(e)); Verify.NotNull(aggregate, nameof(aggregate)); var applyMethods = GetKnownApplyMethods(aggregate); var applyMethod = GetApplyMethod(aggregate, e, applyMethods); Log.Trace("Applying event {0} to aggregate {1}", e, aggregate); applyMethod(aggregate, e); }
/// <summary> /// When overriden, defines the custom behavior to be invoked after attempting to save the specified <paramref name="aggregate"/>. /// </summary> /// <param name="aggregate">The modified <see cref="Aggregate"/> instance if <paramref name="commit"/> is not <value>null</value>; otherwise the original <see cref="Aggregate"/> instance if <paramref name="error"/> is not <value>null</value>.</param> /// <param name="commit">The <see cref="Commit"/> generated if the save was successful; otherwise <value>null</value>.</param> /// <param name="error">The <see cref="Exception"/> thrown if the save was unsuccessful; otherwise <value>null</value>.</param> public virtual void PostSave(Aggregate aggregate, Commit commit, Exception error) { }
/// <summary> /// When overriden, defines the custom behavior to be invokes after successfully retrieving the specified <paramref name="aggregate"/>. /// </summary> /// <param name="aggregate">The loaded aggregate instance.</param> public virtual void PostGet(Aggregate aggregate) { }
/// <summary> /// Invokes zero or more customized <see cref="PipelineHook.PostGet"/> implementations. /// </summary> /// <param name="aggregate">The loaded aggregate instance.</param> private void InvokePostGetHooks(Aggregate aggregate) { foreach (var pipelineHook in postGetHooks) { Log.Trace("Invoking post-get pipeline hook: {0}", pipelineHook); pipelineHook.PostGet(aggregate); } }
/// <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> /// When overriden, defines the custom behavior to be invoked after attempting to save the specified <paramref name="aggregate"/>. /// </summary> /// <param name="aggregate">The modified <see cref="Aggregate"/> instance if <paramref name="commit"/> is not <value>null</value>; otherwise the original <see cref="Aggregate"/> instance if <paramref name="error"/> is not <value>null</value>.</param> /// <param name="commit">The <see cref="Commit"/> generated if the save was successful; otherwise <value>null</value>.</param> /// <param name="error">The <see cref="Exception"/> thrown if the save was unsuccessful; otherwise <value>null</value>.</param> public override void PostSave(Aggregate aggregate, Commit commit, Exception error) { if (commit?.Id != null) DispatchCommit(commit); else Log.Warn("Commit not dispatched"); }
/// <summary> /// Gets the set of known apply methods for the given <paramref name="aggregate"/> instance. /// </summary> /// <param name="aggregate">The <see cref="Aggregate"/> instance on which the event is to be applied.</param> private ApplyMethodCollection GetKnownApplyMethods(Aggregate aggregate) { Type aggregateType = aggregate.GetType(); ApplyMethodCollection applyMethods; if (!knownApplyMethods.TryGetValue(aggregateType, out applyMethods)) throw new MappingException(Exceptions.AggregateTypeUndiscovered.FormatWith(aggregate.GetType())); return applyMethods; }
/// <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> /// Verify that the retrieved <paramref name="aggregate"/> state has not been corrupted before returning to the caller. /// </summary> /// <param name="aggregate">The loaded aggregate instance.</param> public override void PostGet(Aggregate aggregate) { Verify.NotNull(aggregate, nameof(aggregate)); 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> /// Invokes zero or more customized <see cref="PipelineHook.PostSave"/> implementations. /// </summary> /// <param name="aggregate">The modified <see cref="Aggregate"/> instance if <paramref name="commit"/> is not <value>null</value>; otherwise the original <see cref="Aggregate"/> instance if <paramref name="error"/> is not <value>null</value>.</param> /// <param name="commit">The <see cref="Commit"/> generated if the save was successful; otherwise <value>null</value>.</param> /// <param name="error">The <see cref="Exception"/> thrown if the save was unsuccessful; otherwise <value>null</value>.</param> private void InvokePostSaveHooks(Aggregate aggregate, Commit commit, Exception error) { foreach (var pipelineHook in postSaveHooks) { Log.Trace("Invoking post-save pipeline hook: {0}", pipelineHook); pipelineHook.PostSave(aggregate, commit, error); } }
/// <summary> /// Get the associated apply method for the specified <see cref="Event"/> <paramref name="e"/> from the known <paramref name="applyMethods"/>. /// </summary> /// <param name="aggregate">The <see cref="Aggregate"/> instance on which the event is to be applied.</param> /// <param name="e">The <see cref="Event"/> to be applied.</param> /// <param name="applyMethods">The set of known apply methods for a given aggregate instance</param> private static Action<Aggregate, Event> GetApplyMethod(Aggregate aggregate, Event e, ApplyMethodCollection applyMethods) { Action<Aggregate, Event> applyMethod; Type eventType = e.GetType(); if (applyMethods.TryGetValue(eventType, out applyMethod)) return applyMethod; if (!applyMethods.ApplyOptional) throw new MappingException(Exceptions.AggregateApplyMethodNotFound.FormatWith(aggregate.GetType(), e.GetType())); return VoidApplyMethod; }
/// <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> /// 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> /// Applies all <see cref="Commit.Events"/> to the specified <paramref name="aggregate"/> instance. /// </summary> /// <param name="commit">The <see cref="Commit"/> to be applied to the specified <paramref name="aggregate"/>.</param> /// <param name="aggregate">The <see cref="Aggregate"/> instance for which the commit is to be applied.</param> private void ApplyCommitToAggregate(Commit commit, Aggregate aggregate) { var expectedVersion = aggregate.Version + 1; if (commit.Version != expectedVersion) throw new InvalidOperationException(Exceptions.MissingAggregateCommits.FormatWith(expectedVersion, commit.Version)); aggregate.Version = commit.Version; foreach (var e in commit.Events) { using (new EventContext(aggregate.Id, commit.Headers, e)) aggregateUpdater.Apply(e, 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> /// 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; } }