/// <summary> /// When overriden, defines the custom behavior to be invoked after attempting to save the specified <paramref name="saga"/>. /// </summary> /// <param name="saga">The modified <see cref="Saga"/> instance if <paramref name="error"/> is <value>null</value>; otherwise the original <see cref="Saga"/> instance if <paramref name="error"/> is not <value>null</value>.</param> /// <param name="context">The current <see cref="SagaContext"/> assocaited with the pending saga modifications.</param> /// <param name="error">The <see cref="Exception"/> thrown if the save was unsuccessful; otherwise <value>null</value>.</param> public override void PostSave(Saga saga, SagaContext context, Exception error) { base.PostSave(saga, context, error); if (error is ConcurrencyException) statistics.IncrementConflictCount(); }
/// <summary> /// Invokes zero or more customized <see cref="PipelineHook.PostSave"/> implementations. /// </summary> /// <param name="saga">The modified <see cref="Saga"/> instance if <paramref name="error"/> is <value>null</value>; otherwise the original <see cref="Saga"/> instance if <paramref name="error"/> is not <value>null</value>.</param> /// <param name="context">The current <see cref="SagaContext"/> assocaited with the pending saga modifications.</param> /// <param name="error">The <see cref="Exception"/> thrown if the save was unsuccessful; otherwise <value>null</value>.</param> private void InvokePostSaveHooks(Saga saga, SagaContext context, Exception error) { foreach (var pipelineHook in postSaveHooks) { Log.Trace("Invoking post-save pipeline hook: {0}", pipelineHook); pipelineHook.PostSave(saga, context, error); } }
/// <summary> /// Invokes zero or more customized <see cref="PipelineHook.PreSave"/> implementations. /// </summary> /// <param name="saga">The saga to be modified by the current <paramref name="context"/>.</param> /// <param name="context">The current <see cref="SagaContext"/> associated with the pending saga modifications.</param> private void InvokePreSaveHooks(Saga saga, SagaContext context) { foreach (var pipelineHook in preSaveHooks) { Log.Trace("Invoking pre-save pipeline hook: {0}", pipelineHook); pipelineHook.PreSave(saga, context); } }
/// <summary> /// Invokes zero or more customized <see cref="PipelineHook.PostGet"/> implementations. /// </summary> /// <param name="saga">The loaded saga instance.</param> private void InvokePostGetHooks(Saga saga) { foreach (var pipelineHook in postGetHooks) { Log.Trace("Invoking post-get pipeline hook: {0}", pipelineHook); pipelineHook.PostGet(saga); } }
/// <summary> /// Attempt to retrieve an existing saga instance identified by the specified <paramref name="type"/> and <paramref name="id"/>. /// </summary> /// <param name="type">The type of saga to be retrieved.</param> /// <param name="id">The correlation id of the saga to be retrieved.</param> /// <param name="saga">The <see cref="Saga"/> instance if found; otherwise <value>null</value>.</param> public Boolean TryGetSaga(Type type, Guid id, out Saga saga) { Boolean result; InvokePreGetHooks(type, id); result = sagaStore.TryGetSaga(type, id, out saga); InvokePostGetHooks(saga); return(result); }
/// <summary> /// Save the specified <paramref name="context"/> changes for the given <paramref name="saga"/>. /// </summary> /// <param name="saga">The current saga version for which the context applies.</param> /// <param name="context">The saga context containing the saga changes to be applied.</param> public Saga Save(Saga saga, SagaContext context) { InvokePreSaveHooks(saga, context); try { var result = sagaStore.Save(saga, context); InvokePostSaveHooks(result, context, null); return(result); } catch (Exception ex) { InvokePostSaveHooks(saga, context, ex); throw; } }
/// <summary> /// When overriden, defines the custom behavior to be invoked after attempting to save the specified <paramref name="saga"/>. /// </summary> /// <param name="saga">The modified <see cref="Saga"/> instance if <paramref name="error"/> is <value>null</value>; otherwise the original <see cref="Saga"/> instance if <paramref name="error"/> is not <value>null</value>.</param> /// <param name="context">The current <see cref="SagaContext"/> assocaited with the pending saga modifications.</param> /// <param name="error">The <see cref="Exception"/> thrown if the save was unsuccessful; otherwise <value>null</value>.</param> public override void PostSave(Saga saga, SagaContext context, Exception error) { if (!context.TimeoutChanged || saga == null || error != null) { return; } if (saga.Timeout.HasValue && !saga.Completed) { var timeout = saga.Timeout.Value; TimeoutCache.ScheduleTimeout(new SagaTimeout(saga.GetType(), saga.CorrelationId, timeout)); } else { TimeoutCache.ClearTimeout(new SagaReference(saga.GetType(), saga.CorrelationId)); } RescheduleTimer(TimeoutCache.GetNextScheduledTimeout()); }
/// <summary> /// Handles the <paramref name="saga"/> event. /// </summary> /// <param name="saga">The saga instance handling the event.</param> /// <param name="e">The event to be handled.</param> protected override void HandleSagaEvent(Saga saga, Event e) { var timeout = ((Timeout)e).Scheduled; if (saga.Timeout.HasValue) { if (saga.Timeout.Value == timeout) { saga.ClearTimeout(); base.HandleSagaEvent(saga, e); } else { Log.Warn("{0} received unexpected timeout at {1} when scheduled timeout is for {2}", saga, timeout.ToString(DateTimeFormat.RoundTrip), saga.Timeout.Value.ToString(DateTimeFormat.RoundTrip)); } } else { Log.Warn("{0} received unexpected timeout at {1} when no timeout is scheduled", saga, timeout.ToString(DateTimeFormat.RoundTrip)); } }
/// <summary> /// Attempt to retrieve an existing saga instance identified by the specified <paramref name="type"/> and <paramref name="id"/>. /// </summary> /// <param name="type">The type of saga to be retrieved.</param> /// <param name="id">The correlation id of the saga to be retrieved.</param> /// <param name="saga">The <see cref="Saga"/> instance if found; otherwise <value>null</value>.</param> public Boolean TryGetSaga(Type type, Guid id, out Saga saga) { Verify.NotNull(type, nameof(type)); var key = String.Concat(type.GetFullNameWithAssembly(), "-", id); using (var sagaLock = new SagaLock(type, id)) { sagaLock.Aquire(); //NOTE: We do not want to cache a NULL saga reference, thus explicitly check for existance and add to cache if instance found. saga = (Saga)memoryCache.Get(key); if (saga == null && sagaStore.TryGetSaga(type, id, out saga)) { memoryCache.Add(key, saga, CreateCacheItemPolicy()); } } //NOTE: Given that saga state is modified during `Handling`, we must always return a copy of the cached instance. return((saga == null ? null : saga = saga.Copy()) != null); }
/// <summary> /// Attempt to retrieve an existing saga instance identified by the specified <paramref name="type"/> and <paramref name="id"/>. /// </summary> /// <param name="type">The type of saga to be retrieved.</param> /// <param name="id">The correlation id of the saga to be retrieved.</param> /// <param name="saga">The <see cref="Saga"/> instance if found; otherwise <value>null</value>.</param> public Boolean TryGetSaga(Type type, Guid id, out Saga saga) { Verify.NotNull(type, nameof(type)); var key = String.Concat(type.GetFullNameWithAssembly(), "-", id); using (var sagaLock = new SagaLock(type, id)) { sagaLock.Aquire(); //NOTE: We do not want to cache a NULL saga reference, thus explicitly check for existance and add to cache if instance found. saga = (Saga)memoryCache.Get(key); if (saga == null && sagaStore.TryGetSaga(type, id, out saga)) memoryCache.Add(key, saga, CreateCacheItemPolicy()); } //NOTE: Given that saga state is modified during `Handling`, we must always return a copy of the cached instance. return (saga == null ? null : saga = saga.Copy()) != null; }
/// <summary> /// Save the specified <paramref name="context"/> changes for the given <paramref name="saga"/>. /// </summary> /// <param name="saga">The current saga version for which the context applies.</param> /// <param name="context">The saga context containing the saga changes to be applied.</param> public Saga Save(Saga saga, SagaContext context) { Verify.NotNull(saga, nameof(saga)); Verify.NotNull(context, nameof(context)); var sagaType = saga.GetType(); var key = String.Concat(sagaType.GetFullNameWithAssembly(), "-", saga.CorrelationId); using (var sagaLock = new SagaLock(sagaType, saga.CorrelationId)) { sagaLock.Aquire(); try { sagaStore.Save(saga, context); if (saga.Completed) memoryCache.Remove(key); else memoryCache.Set(key, saga, CreateCacheItemPolicy()); return saga; } catch (ConcurrencyException) { memoryCache.Remove(key); throw; } } }
/// <summary> /// Handles the <paramref name="saga"/> event. /// </summary> /// <param name="saga">The saga instance handling the event.</param> /// <param name="e">The event to be handled.</param> protected virtual void HandleSagaEvent(Saga saga, Event e) { Log.Trace("{0} handling event {1}", saga, e); Executor(saga, e); }
/// <summary> /// Attempt to retrieve an existing saga instance identified by the specified <paramref name="type"/> and <paramref name="id"/>. /// </summary> /// <param name="type">The type of saga to be retrieved.</param> /// <param name="id">The correlation id of the saga to be retrieved.</param> /// <param name="saga">The <see cref="Saga"/> instance if found; otherwise <value>null</value>.</param> public Boolean TryGetSaga(Type type, Guid id, out Saga saga) { Boolean result; InvokePreGetHooks(type, id); result = sagaStore.TryGetSaga(type, id, out saga); InvokePostGetHooks(saga); return result; }
/// <summary> /// Attempt to retrieve an existing saga instance identified by the specified <paramref name="type"/> and <paramref name="id"/>. /// </summary> /// <param name="type">The type of saga to be retrieved.</param> /// <param name="id">The correlation id of the saga to be retrieved.</param> /// <param name="saga">The <see cref="Saga"/> instance if found; otherwise <value>null</value>.</param> public Boolean TryGetSaga(Type type, Guid id, out Saga saga) { var result = sagaStore.TryGetSaga(type, id, out saga); statistics.IncrementQueryCount(); return result; }
/// <summary> /// When overriden, defines the custom behavior to be invoked prior to saving the specified <paramref name="saga"/>. /// </summary> /// <param name="saga">The saga to be modified using the current <paramref name="context"/>.</param> /// <param name="context">The current <see cref="SagaContext"/> assocaited with the pending saga modifications.</param> public virtual void PreSave(Saga saga, SagaContext context) { }
/// <summary> /// Save the specified <paramref name="context"/> changes for the given <paramref name="saga"/>. /// </summary> /// <param name="saga">The current saga version for which the context applies.</param> /// <param name="context">The saga context containing the saga changes to be applied.</param> public Saga Save(Saga saga, SagaContext context) { InvokePreSaveHooks(saga, context); try { var result = sagaStore.Save(saga, context); InvokePostSaveHooks(result, context, null); return result; } catch (Exception ex) { InvokePostSaveHooks(saga, context, ex); throw; } }
/// <summary> /// When overriden, defines the custom behavior to be invokes after successfully retrieving the specified <paramref name="saga"/>. /// </summary> /// <param name="saga">The loaded saga instance.</param> public virtual void PostGet(Saga saga) { }
/// <summary> /// Save the specified <paramref name="context"/> changes for the given <paramref name="saga"/>. /// </summary> /// <param name="saga">The current saga version for which the context applies.</param> /// <param name="context">The saga context containing the saga changes to be applied.</param> public Saga Save(Saga saga, SagaContext context) { var result = sagaStore.Save(saga, context); if (saga.Version == 1) { statistics.IncrementInsertCount(); } else { if (saga.Completed) { statistics.IncrementDeleteCount(); } else { statistics.IncrementUpdateCount(); } } return result; }
/// <summary> /// When overriden, defines the custom behavior to be invoked after attempting to save the specified <paramref name="saga"/>. /// </summary> /// <param name="saga">The modified <see cref="Saga"/> instance if <paramref name="error"/> is <value>null</value>; otherwise the original <see cref="Saga"/> instance if <paramref name="error"/> is not <value>null</value>.</param> /// <param name="context">The current <see cref="SagaContext"/> assocaited with the pending saga modifications.</param> /// <param name="error">The <see cref="Exception"/> thrown if the save was unsuccessful; otherwise <value>null</value>.</param> public virtual void PostSave(Saga saga, SagaContext context, Exception error) { }
/// <summary> /// When overriden, defines the custom behavior to be invoked after attempting to save the specified <paramref name="saga"/>. /// </summary> /// <param name="saga">The modified <see cref="Saga"/> instance if <paramref name="error"/> is <value>null</value>; otherwise the original <see cref="Saga"/> instance if <paramref name="error"/> is not <value>null</value>.</param> /// <param name="context">The current <see cref="SagaContext"/> assocaited with the pending saga modifications.</param> /// <param name="error">The <see cref="Exception"/> thrown if the save was unsuccessful; otherwise <value>null</value>.</param> public override void PostSave(Saga saga, SagaContext context, Exception error) { if (!context.TimeoutChanged || saga == null || error != null) return; if (saga.Timeout.HasValue && !saga.Completed) { var timeout = saga.Timeout.Value; TimeoutCache.ScheduleTimeout(new SagaTimeout(saga.GetType(), saga.CorrelationId, timeout)); } else { TimeoutCache.ClearTimeout(new SagaReference(saga.GetType(), saga.CorrelationId)); } RescheduleTimer(TimeoutCache.GetNextScheduledTimeout()); }