예제 #1
0
        /// <summary>
        /// Retrieves a <see cref="IContainSagaData"/> instance. Used when implementing a <see cref="IFindSagas{T}"/>.
        /// </summary>
        /// <typeparam name="TSagaData">The <see cref="IContainSagaData"/> type to return.</typeparam>
        /// <param name="session">Used to provide an extension point and access the current <see cref="DbConnection"/> and <see cref="DbTransaction"/>.</param>
        /// <param name="context">Used to append a concurrency value that can be verified when the SagaData is persisted.</param>
        /// <param name="whereClause">The SQL where clause to append to the retrieve saga SQL statement.</param>
        /// <param name="appendParameters">Used to append <see cref="DbParameter"/>s used in the <paramref name="whereClause"/>.</param>
        public static Task <TSagaData> GetSagaData <TSagaData>(this SynchronizedStorageSession session, ReadOnlyContextBag context, string whereClause, ParameterAppender appendParameters)
            where TSagaData : class, IContainSagaData
        {
            Guard.AgainstNull(nameof(session), session);
            Guard.AgainstNull(nameof(context), context);
            Guard.AgainstNull(nameof(appendParameters), appendParameters);
            Guard.AgainstNullAndEmpty(nameof(whereClause), whereClause);
            var writableContextBag = (ContextBag)context;
            var sqlSession         = session.GetSqlStorageSession();

            return(SagaPersister.GetByWhereClause <TSagaData>(whereClause, session, writableContextBag, appendParameters, sqlSession.InfoCache));
        }
        /// <summary>
        /// Retrieves a <see cref="IContainSagaData"/> instance. Used when implementing a <see cref="IFindSagas{T}"/>.
        /// </summary>
        /// <typeparam name="TSagaData">The <see cref="IContainSagaData"/> type to return.</typeparam>
        /// <param name="session">Used to provide an extension point and access the current <see cref="DbConnection"/> and <see cref="DbTransaction"/>.</param>
        /// <param name="context">Used to append a concurrency value that can be verified when the SagaData is persisted.</param>
        /// <param name="whereClause">The SQL where clause to append to the retrieve saga SQL statement.</param>
        /// <param name="appendParameters">Used to append <see cref="DbParameter"/>s used in the <paramref name="whereClause"/>.</param>
        public static Task <TSagaData> GetSagaData <TSagaData>(this SynchronizedStorageSession session, ReadOnlyContextBag context, string whereClause, ParameterAppender appendParameters)
            where TSagaData : class, IContainSagaData
        {
            Guard.AgainstNull(nameof(session), session);
            Guard.AgainstNull(nameof(context), context);
            Guard.AgainstNull(nameof(appendParameters), appendParameters);
            Guard.AgainstNullAndEmpty(nameof(whereClause), whereClause);

            var writableContextBag = (ContextBag)context;
            var sqlSession         = session.GetSqlStorageSession();

            if (sqlSession.InfoCache == null)
            {
                throw new Exception("Cannot load saga data because the Sagas feature is disabled in the endpoint.");
            }
            return(SagaPersister.GetByWhereClause <TSagaData>(whereClause, session, writableContextBag, appendParameters, sqlSession.InfoCache));
        }
        /// <summary>
        /// Retrieves a <see cref="IContainSagaData"/> instance. Used when implementing a <see cref="IFindSagas{T}"/>.
        /// </summary>
        /// <typeparam name="TSagaData">The <see cref="IContainSagaData"/> type to return.</typeparam>
        /// <param name="session">Used to provide an extension point and access the current <see cref="DbConnection"/> and <see cref="DbTransaction"/>.</param>
        /// <param name="context">Used to append a concurrency value that can be verified when the SagaData is persisted.</param>
        /// <param name="whereClause">The SQL where clause to append to the retrieve saga SQL statement.</param>
        /// <param name="appendParameters">Used to append <see cref="DbParameter"/>s used in the <paramref name="whereClause"/>.</param>
        public static Task <TSagaData> GetSagaData <TSagaData>(this SynchronizedStorageSession session, ReadOnlyContextBag context, string whereClause, ParameterAppender appendParameters)
            where TSagaData : IContainSagaData
        {
            Guard.AgainstNull(nameof(session), session);
            Guard.AgainstNull(nameof(context), context);
            Guard.AgainstNull(nameof(appendParameters), appendParameters);
            Guard.AgainstNullAndEmpty(nameof(whereClause), whereClause);
            var writableContextBag = (ContextBag)context;
            var sqlSession         = session.GetSqlStorageSession();
            var sagaInfoCache      = sqlSession.InfoCache;

            if (sagaInfoCache == null)
            {
                throw new Exception($"{nameof(GetSagaData)} can only be executed when Sagas have been enabled on the endpoint.");
            }
            return(SagaPersister.GetByWhereClause <TSagaData>(whereClause, session, writableContextBag, appendParameters, sagaInfoCache));
        }
        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);
                }
            }
        }
예제 #5
0
        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);
                }
            }
        }