public async Task Invoke(IInvokeHandlerContext context, Func <IInvokeHandlerContext, Task> next) { var isTimeoutMessage = IsTimeoutMessage(context.Headers); var isTimeoutHandler = context.MessageHandler.IsTimeoutHandler; if (isTimeoutHandler && !isTimeoutMessage) { return; } if (!isTimeoutHandler && isTimeoutMessage) { return; } RemoveSagaHeadersIfProcessingAEvent(context); if (!(context.MessageHandler.Instance is Saga saga)) { await next(context).ConfigureAwait(false); return; } var currentSagaMetadata = sagaMetadataCollection.Find(context.MessageHandler.Instance.GetType()); if (context.Headers.TryGetValue(Headers.SagaType, out var targetSagaTypeString) && context.Headers.TryGetValue(Headers.SagaId, out var targetSagaId)) { var targetSagaType = Type.GetType(targetSagaTypeString, false); if (targetSagaType == null) { logger.Warn($"Saga headers indicated that the message was intended for {targetSagaTypeString} but that type isn't available. Will fallback to query persister for a saga instance of type {currentSagaMetadata.SagaType.FullName} and saga id {targetSagaId} instead"); } else { if (!sagaMetadataCollection.TryFind(targetSagaType, out var targetSagaMetaData)) { logger.Warn($"Saga headers indicated that the message was intended for {targetSagaType.FullName} but no metadata was found for that saga type. Will fallback to query persister for a saga instance of type {currentSagaMetadata.SagaType.FullName} and saga id {targetSagaId} instead"); } else { if (targetSagaMetaData.SagaType != currentSagaMetadata.SagaType) { //Message was intended for a different saga so no need to continue with this invocation return; } } } } var sagaInstanceState = new ActiveSagaInstance(saga, currentSagaMetadata, () => DateTimeOffset.UtcNow); //so that other behaviors can access the saga context.Extensions.Set(sagaInstanceState); var loadedEntity = await TryLoadSagaEntity(currentSagaMetadata, context).ConfigureAwait(false); if (loadedEntity == null) { if (IsMessageAllowedToStartTheSaga(context, currentSagaMetadata)) { context.Extensions.Get <SagaInvocationResult>().SagaFound(); sagaInstanceState.AttachNewEntity(CreateNewSagaEntity(currentSagaMetadata, context)); } else { if (!context.Headers.ContainsKey(Headers.SagaId)) { var finderDefinition = GetSagaFinder(currentSagaMetadata, context); if (finderDefinition == null) { throw new Exception($"Message type {context.MessageBeingHandled.GetType().Name} is handled by saga {currentSagaMetadata.SagaType.Name}, but the saga does not contain a property mapping or custom saga finder to map the message to saga data. Consider adding a mapping in the saga's {nameof(Saga.ConfigureHowToFindSaga)} method."); } } sagaInstanceState.MarkAsNotFound(); //we don't invoke not found handlers for timeouts if (isTimeoutMessage) { context.Extensions.Get <SagaInvocationResult>().SagaFound(); logger.InfoFormat("No saga found for timeout message {0}, ignoring since the saga has been marked as complete before the timeout fired", context.MessageId); } else { context.Extensions.Get <SagaInvocationResult>().SagaNotFound(); } } } else { context.Extensions.Get <SagaInvocationResult>().SagaFound(); sagaInstanceState.AttachExistingEntity(loadedEntity); } await next(context).ConfigureAwait(false); if (sagaInstanceState.NotFound) { return; } if (saga.Completed) { if (!sagaInstanceState.IsNew) { await sagaPersister.Complete(saga.Entity, context.SynchronizedStorageSession, context.Extensions, context.CancellationToken).ConfigureAwait(false); } logger.DebugFormat("Saga: '{0}' with Id: '{1}' has completed.", sagaInstanceState.Metadata.Name, saga.Entity.Id); sagaInstanceState.Completed(); } else { sagaInstanceState.ValidateChanges(); if (sagaInstanceState.IsNew) { var sagaCorrelationProperty = SagaCorrelationProperty.None; if (sagaInstanceState.TryGetCorrelationProperty(out var correlationProperty)) { sagaCorrelationProperty = new SagaCorrelationProperty(correlationProperty.PropertyInfo.Name, correlationProperty.PropertyInfo.GetValue(sagaInstanceState.Instance.Entity)); } await sagaPersister.Save(saga.Entity, sagaCorrelationProperty, context.SynchronizedStorageSession, context.Extensions, context.CancellationToken).ConfigureAwait(false); } else { await sagaPersister.Update(saga.Entity, context.SynchronizedStorageSession, context.Extensions, context.CancellationToken).ConfigureAwait(false); } sagaInstanceState.Updated(); } }
public async Task Invoke(IInvokeHandlerContext context, Func <IInvokeHandlerContext, Task> next) { var isTimeoutMessage = IsTimeoutMessage(context.Headers); var isTimeoutHandler = context.MessageHandler.IsTimeoutHandler; if (isTimeoutHandler && !isTimeoutMessage) { return; } if (!isTimeoutHandler && isTimeoutMessage) { return; } currentContext = context; RemoveSagaHeadersIfProcessingAEvent(context); var saga = context.MessageHandler.Instance as Saga; if (saga == null) { await next(context).ConfigureAwait(false); return; } var sagaMetadata = sagaMetadataCollection.Find(context.MessageHandler.Instance.GetType()); var sagaInstanceState = new ActiveSagaInstance(saga, sagaMetadata, () => DateTime.UtcNow); //so that other behaviors can access the saga context.Extensions.Set(sagaInstanceState); var loadedEntity = await TryLoadSagaEntity(sagaMetadata, context).ConfigureAwait(false); if (loadedEntity == null) { //if this message are not allowed to start the saga if (IsMessageAllowedToStartTheSaga(context, sagaMetadata)) { context.Extensions.Get <SagaInvocationResult>().SagaFound(); sagaInstanceState.AttachNewEntity(CreateNewSagaEntity(sagaMetadata, context)); } else { if (!context.Headers.ContainsKey(Headers.SagaId)) { var finderDefinition = GetSagaFinder(sagaMetadata, context); if (finderDefinition == null) { throw new Exception($"Message type {context.MessageBeingHandled.GetType().Name} is handled by saga {sagaMetadata.SagaType.Name}, but the saga does not contain a property mapping or custom saga finder to map the message to saga data. Consider adding a mapping in the saga's {nameof(Saga.ConfigureHowToFindSaga)} method."); } } sagaInstanceState.MarkAsNotFound(); //we don't invoke not found handlers for timeouts if (isTimeoutMessage) { context.Extensions.Get <SagaInvocationResult>().SagaFound(); logger.InfoFormat("No saga found for timeout message {0}, ignoring since the saga has been marked as complete before the timeout fired", context.MessageId); } else { context.Extensions.Get <SagaInvocationResult>().SagaNotFound(); } } } else { context.Extensions.Get <SagaInvocationResult>().SagaFound(); sagaInstanceState.AttachExistingEntity(loadedEntity); } await next(context).ConfigureAwait(false); if (sagaInstanceState.NotFound) { return; } if (saga.Completed) { if (!sagaInstanceState.IsNew) { await sagaPersister.Complete(saga.Entity, context.SynchronizedStorageSession, context.Extensions).ConfigureAwait(false); } if (saga.Entity.Id != Guid.Empty) { await timeoutCancellation.CancelDeferredMessages(saga.Entity.Id.ToString(), context).ConfigureAwait(false); } logger.DebugFormat("Saga: '{0}' with Id: '{1}' has completed.", sagaInstanceState.Metadata.Name, saga.Entity.Id); sagaInstanceState.Completed(); } else { sagaInstanceState.ValidateChanges(); if (sagaInstanceState.IsNew) { ActiveSagaInstance.CorrelationPropertyInfo correlationProperty; var sagaCorrelationProperty = SagaCorrelationProperty.None; if (sagaInstanceState.TryGetCorrelationProperty(out correlationProperty)) { sagaCorrelationProperty = new SagaCorrelationProperty(correlationProperty.PropertyInfo.Name, correlationProperty.Value); } await sagaPersister.Save(saga.Entity, sagaCorrelationProperty, context.SynchronizedStorageSession, context.Extensions).ConfigureAwait(false); } else { await sagaPersister.Update(saga.Entity, context.SynchronizedStorageSession, context.Extensions).ConfigureAwait(false); } sagaInstanceState.Updated(); } }
public void Invoke(IncomingContext context, Action next) { currentContext = context; // We need this for backwards compatibility because in v4.0.0 we still have this headers being sent as part of the message even if MessageIntent == MessageIntentEnum.Publish if (context.PhysicalMessage.MessageIntent == MessageIntentEnum.Publish) { context.PhysicalMessage.Headers.Remove(Headers.SagaId); context.PhysicalMessage.Headers.Remove(Headers.SagaType); } var saga = context.MessageHandler.Instance as Saga.Saga; if (saga == null) { next(); return; } var sagaMetadata = SagaMetaModel.FindByName(context.MessageHandler.Instance.GetType().FullName); var sagaInstanceState = new ActiveSagaInstance(saga, sagaMetadata); //so that other behaviors can access the saga context.Set(sagaInstanceState); var loadedEntity = TryLoadSagaEntity(sagaMetadata, context.IncomingLogicalMessage); if (loadedEntity == null) { //if this message are not allowed to start the saga if (IsMessageAllowedToStartTheSaga(context.IncomingLogicalMessage, sagaMetadata)) { sagaInstanceState.AttachNewEntity(CreateNewSagaEntity(sagaMetadata, context.IncomingLogicalMessage)); } else { sagaInstanceState.MarkAsNotFound(); //we don't invoke not found handlers for timeouts since if (IsTimeoutMessage(context.IncomingLogicalMessage)) { logger.InfoFormat("No saga found for timeout message {0}, ignoring since the saga has been marked as complete before the timeout fired", context.PhysicalMessage.Id); } else { InvokeSagaNotFoundHandlers(sagaMetadata); } } } else { sagaInstanceState.AttachExistingEntity(loadedEntity); } if (IsTimeoutMessage(context.IncomingLogicalMessage)) { context.MessageHandler.Invocation = MessageHandlerRegistry.InvokeTimeout; } next(); if (sagaInstanceState.NotFound) { return; } sagaInstanceState.ValidateIdHasNotChanged(); LogSaga(sagaInstanceState, context); if (saga.Completed) { if (!sagaInstanceState.IsNew) { SagaPersister.Complete(saga.Entity); } if (saga.Entity.Id != Guid.Empty) { NotifyTimeoutManagerThatSagaHasCompleted(saga); } logger.DebugFormat("Saga: '{0}' with Id: '{1}' has completed.", sagaInstanceState.Metadata.Name, saga.Entity.Id); } else { if (sagaInstanceState.IsNew) { SagaPersister.Save(saga.Entity); } else { SagaPersister.Update(saga.Entity); } } }
public void Invoke(IncomingContext context, Action next) { // We need this for backwards compatibility because in v4.0.0 we still have this headers being sent as part of the message even if MessageIntent == MessageIntentEnum.Publish if (context.PhysicalMessage.MessageIntent == MessageIntentEnum.Publish) { context.PhysicalMessage.Headers.Remove(Headers.SagaId); context.PhysicalMessage.Headers.Remove(Headers.SagaType); } var saga = context.MessageHandler.Instance as Saga.Saga; if (saga == null) { next(); return; } currentContext = context; var sagaInstanceState = new ActiveSagaInstance(saga); //so that other behaviors can access the saga context.Set(sagaInstanceState); var loadedEntity = TryLoadSagaEntity(saga, context.IncomingLogicalMessage); if (loadedEntity == null) { //if this message are not allowed to start the saga if (IsAllowedToStartANewSaga(context, sagaInstanceState)) { sagaInstanceState.AttachNewEntity(CreateNewSagaEntity(sagaInstanceState.SagaType)); } else { sagaInstanceState.MarkAsNotFound(); InvokeSagaNotFoundHandlers(sagaInstanceState.SagaType); } } else { sagaInstanceState.AttachExistingEntity(loadedEntity); } if (IsTimeoutMessage(context.IncomingLogicalMessage)) { context.MessageHandler.Invocation = MessageHandlerRegistry.InvokeTimeout; } next(); if (sagaInstanceState.NotFound) { return; } LogSaga(sagaInstanceState, context); if (saga.Completed) { if (!sagaInstanceState.IsNew) { SagaPersister.Complete(saga.Entity); } if (saga.Entity.Id != Guid.Empty) { NotifyTimeoutManagerThatSagaHasCompleted(saga); } logger.Debug(string.Format("Saga: '{0}' with Id: '{1}' has completed.", sagaInstanceState.SagaType.FullName, saga.Entity.Id)); } else { if (sagaInstanceState.IsNew) { SagaPersister.Save(saga.Entity); } else { SagaPersister.Update(saga.Entity); } } }