/// <summary>
 /// Initializes a new instance of the <see cref="PipelineContext"/> class.
 /// </summary>
 /// <param name="message">The message.</param>
 /// <param name="saga">The saga.</param>
 /// <param name="operationResult">The operation result. Null of operation have not finished</param>
 public PipelineContext(ISagaMessage message, IAccessibleSaga saga, OperationResult operationResult = null)
 {
     this.Message        = message;
     this.AccessibleSaga = saga;
     SagaData            = NSagaReflection.Get(saga, "SagaData");
     OperationResult     = operationResult;
 }
        /// <summary>
        /// Consumes the specified saga message - finds the existing saga that can consume given message type and with matching CorrelationId.
        /// </summary>
        /// <param name="sagaMessage">The saga message.</param>
        /// <returns>
        /// Result of the operation
        /// </returns>
        /// <exception cref="System.ArgumentException"></exception>
        public OperationResult Consume(ISagaMessage sagaMessage)
        {
            Guard.CheckSagaMessage(sagaMessage, nameof(sagaMessage));

            var resolvedSaga = sagaFactory.ResolveSagaConsumedBy(sagaMessage);
            var sagaType     = resolvedSaga.GetType();

            var saga = NSagaReflection.InvokeGenericMethod(sagaRepository, "Find", sagaType, sagaMessage.CorrelationId);

            if (saga == null)
            {
                throw new ArgumentException($"Saga with this CorrelationId does not exist. Please initiate a saga with IInitiatingMessage. {sagaMessage.CorrelationId}");
            }

            pipelineHook.BeforeConsuming(new PipelineContext(sagaMessage, (IAccessibleSaga)saga));

            var errors = (OperationResult)NSagaReflection.InvokeMethod(saga, "Consume", sagaMessage);

            pipelineHook.AfterConsuming(new PipelineContext(sagaMessage, (IAccessibleSaga)saga, errors));

            if (errors.IsSuccessful)
            {
                sagaRepository.Save((IAccessibleSaga)saga);
                pipelineHook.AfterSave(new PipelineContext(sagaMessage, (IAccessibleSaga)saga, errors));
            }

            return(errors);
        }
示例#3
0
        public IAccessibleSaga ResolveSagaConsumedBy(ISagaMessage message)
        {
            var interfaceType = typeof(ConsumerOf <>).MakeGenericType(message.GetType());
            var saga          = container.Resolve(interfaceType);

            return((IAccessibleSaga)saga);
        }
示例#4
0
        public OperationResult Consume(ISagaMessage sagaMessage)
        {
            if (sagaMessage.CorrelationId == default(Guid))
            {
                throw new ArgumentException("CorrelationId was not provided in the message. Please make sure you assign CorrelationId before initiating your Saga");
            }

            var sagaTypes = Reflection.GetSagaTypesConsuming(sagaMessage, assembliesToScan);

            if (!sagaTypes.Any())
            {
                throw new ArgumentException($"Message of type {sagaMessage.GetType().Name} is not consumed by any Sagas. Please add ConsumerOf<{sagaMessage.GetType().Name}> to your Saga type");
            }
            if (sagaTypes.Count() > 1)
            {
                // can't have multiple sagas consumed by the same message
                var sagaNames = String.Join(", ", sagaTypes.Select(t => t.Name));
                throw new ArgumentException($"Message of type {sagaMessage.GetType().Name} is consumed by more than one saga. Please make sure any single message is consumed by only one saga. Affected sagas: {sagaNames}");
            }

            var sagaType = sagaTypes.First();
            var saga     = Reflection.InvokeGenericMethod(sagaRepository, "Find", sagaType, sagaMessage.CorrelationId);

            if (saga == null)
            {
                throw new ArgumentException($"Saga with this CorrelationId does not exist. Please initiate a saga with IInitiatingMessage.");
            }

            var errors = (OperationResult)Reflection.InvokeMethod(saga, "Consume", sagaMessage);

            sagaRepository.Save(saga); // now the real question - should we persist the saga if operation returned errors? Probably should be configurable

            return(errors);
        }
示例#5
0
        /// <summary>
        /// Return all saga types that consume this type of message
        /// </summary>
        /// <param name="message">Initialisation message to check for</param>
        /// <param name="assemblies">Assemblies to scan for sagas</param>
        /// <returns></returns>
        internal static IEnumerable <Type> GetSagaTypesConsuming(ISagaMessage message, params Assembly[] assemblies)
        {
            try
            {
                if (assemblies.Length == 0)
                {
                    assemblies = AppDomain.CurrentDomain.GetAssemblies();
                }

                var messageType             = message.GetType();
                var initiatingInterfaceType = typeof(ConsumerOf <>).MakeGenericType(messageType);

                var scan = assemblies.SelectMany(a => a.GetTypes())
                           .Where(t => initiatingInterfaceType.IsAssignableFrom(t))
                           .ToList();

                return(scan);
            }
            catch (ReflectionTypeLoadException ex)
            {
                //Display or log the error based on your application.

                throw new Exception(BuildFusionException(ex));
            }
        }
示例#6
0
        /// <summary>
        /// Finds the saga entity type T using the saga Id in the given message.
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public T FindBy(ISagaMessage message)
        {
            if (Persister != null)
            {
                return(Persister.Get <T>(message.SagaId));
            }

            return(default(T));
        }
示例#7
0
        public OperationResult Consume(ISagaMessage sagaMessage)
        {
            var opResult = _sagaMediator.Consume(sagaMessage);

            if (opResult.HasErrors)
            {
                Errors.AddCollection(opResult.Errors);
            }

            return(opResult);
        }
        internal static void CheckSagaMessage(ISagaMessage sagaMessage, string argumentName)
        {
            if (sagaMessage == null)
            {
                throw new ArgumentNullException(argumentName);
            }

            if (sagaMessage.CorrelationId == default(Guid))
            {
                throw new ArgumentException("CorrelationId was not provided in the message. Please make sure you assign CorrelationId before issuing it to your Saga");
            }
        }
示例#9
0
        public Task Persist(SagaContext saga, ISagaMessage sagaMessage, IMessageHandlerContext context)
        {
            if (saga is null)
            {
                throw new ArgumentNullException(nameof(saga), $"A non-null saga is required for persistance.");
            }

            saga.PersistedAtUtc     = DateTime.UtcNow;
            _sagaCache[saga.SagaId] = saga;

            RemoveExpiredMessagesFromSagaPersistance();

            _logger.LogDebug($"Saga persisted to in-memory storage with id '{saga.SagaId}' and status '{saga.Status}'");
            return(Task.CompletedTask);
        }
示例#10
0
        /// <summary>
        /// Return all saga types that consume this type of message
        /// </summary>
        /// <param name="message">Initialisation message to check for</param>
        /// <param name="assemblies">Assemblies to scan for sagas</param>
        /// <returns></returns>
        public static IEnumerable <Type> GetSagaTypesConsuming(ISagaMessage message, params Assembly[] assemblies)
        {
            if (assemblies.Length == 0)
            {
                assemblies = AppDomain.CurrentDomain.GetAssemblies();
            }

            var messageType             = message.GetType();
            var initiatingInterfaceType = typeof(ConsumerOf <>).MakeGenericType(messageType);

            var scan = assemblies.SelectMany(a => a.GetTypes())
                       .Where(t => initiatingInterfaceType.IsAssignableFrom(t))
                       .ToList();

            return(scan);
        }
示例#11
0
 public IAccessibleSaga ResolveSagaConsumedBy(ISagaMessage message)
 {
     throw new NotImplementedException();
 }
示例#12
0
        public async Task InvokeStep <TMessage>(Func <TMessage, IMessageHandlerContext, Task> sagaStepHandler, ISagaMessage message, IMessageBrokerContext context)
            where TMessage : IMessage
        {
            SagaContext saga = await _sagaInitializer.Initialize(message, context).ConfigureAwait(false);

            if (!(sagaStepHandler is null))
            {
                await sagaStepHandler((TMessage)message, context).ConfigureAwait(false);
            }

            saga.InProgress();

            context.BrokeredMessage.WithSagaStatus(saga.Status.Status);

            await _sagaPersister.Persist(saga, message, context).ConfigureAwait(false);
        }
示例#13
0
        public async Task Complete <TMessage>(Func <TMessage, IMessageHandlerContext, Task> sagaStepHandler, ISagaMessage message, IMessageBrokerContext context) where TMessage : IMessage
        {
            SagaContext saga = await _sagaInitializer.Initialize(message, context).ConfigureAwait(false);

            if (!(sagaStepHandler is null))
            {
                await sagaStepHandler((TMessage)message, context).ConfigureAwait(false);
            }

            if (context.Container.TryGet <ErrorContext>(out var errorContext))
            {
                saga.Fail(errorContext.ToString());
            }
            else if (context.Container.TryGet <CompensationRoutingContext>(out var compensateContext))
            {
                saga.Fail(compensateContext.ToString());
            }
            else
            {
                saga.Success();
            }

            context.BrokeredMessage.WithSagaStatus(saga.Status.Status);

            await _sagaPersister.Persist(saga, message, context).ConfigureAwait(false);
        }
示例#14
0
 /// <summary>
 /// Initializes a new instance of the <see cref="ReceivedMessage"/> class.
 /// </summary>
 /// <param name="sagaMessage">The saga message.</param>
 /// <param name="operationResult">The operation result.</param>
 public ReceivedMessage(ISagaMessage sagaMessage, OperationResult operationResult)
 {
     Timestamp       = TimeProvider.Current.UtcNow;
     SagaMessage     = sagaMessage;
     OperationResult = operationResult;
 }
示例#15
0
 /// <summary>
 /// Initializes a new instance of the <see cref="ReceivedMessage"/> class.
 /// </summary>
 /// <param name="sagaMessage">The saga message.</param>
 public ReceivedMessage(ISagaMessage sagaMessage)
 {
     Timestamp   = TimeProvider.Current.UtcNow;
     SagaMessage = sagaMessage;
 }