/// <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);
        }
예제 #2
0
        /// <summary>
        /// Finds and returns saga instance with the given correlation ID.
        /// You will get exceptions if TSaga does not match the actual saga data with the provided exception.
        ///
        /// Actually creates an instance of saga from service locator, retrieves SagaData and Headers from the storage and populates the instance with these.
        /// </summary>
        /// <typeparam name="TSaga">Type of saga we are looking for</typeparam>
        /// <param name="correlationId">CorrelationId to identify the saga</param>
        /// <returns>An instance of the saga. Or Null if there is no saga with this ID.</returns>
        public TSaga Find <TSaga>(Guid correlationId) where TSaga : class, IAccessibleSaga
        {
            Guard.ArgumentIsNotNull(correlationId, nameof(correlationId));

            using (var connection = connectionFactory.CreateOpenConnection())
                using (var database = new Database(connection))
                {
                    var sql           = Sql.Builder.Where("correlationId = @0", correlationId);
                    var persistedData = database.SingleOrDefault <SagaData>(sql);

                    if (persistedData == null)
                    {
                        return(null);
                    }

                    var sagaInstance = sagaFactory.ResolveSaga <TSaga>();
                    var sagaDataType = NSagaReflection.GetInterfaceGenericType <TSaga>(typeof(ISaga <>));
                    var sagaData     = messageSerialiser.Deserialise(persistedData.BlobData, sagaDataType);

                    var headersSql       = Sql.Builder.Where("correlationId = @0", correlationId);
                    var headersPersisted = database.Query <SagaHeaders>(headersSql);
                    var headers          = headersPersisted.ToDictionary(k => k.Key, v => v.Value);

                    NSagaReflection.Set(sagaInstance, "CorrelationId", correlationId);
                    NSagaReflection.Set(sagaInstance, "SagaData", sagaData);
                    NSagaReflection.Set(sagaInstance, "Headers", headers);

                    return(sagaInstance);
                }
        }
        /// <summary>
        /// Finds and returns saga instance with the given correlation ID.
        /// Actually creates an instance of saga from Saga factory, retrieves SagaData and Headers from the storage and populates the instance with these.
        /// </summary>
        /// <typeparam name="TSaga">Type of saga we are looking for</typeparam>
        /// <param name="correlationId">CorrelationId to identify the saga</param>
        /// <returns>
        /// An instance of the saga. Or Null if there is no saga with this ID.
        /// </returns>
        public TSaga Find <TSaga>(Guid correlationId) where TSaga : class, IAccessibleSaga
        {
            string dataSerialised;

            if (!DataDictionary.TryGetValue(correlationId, out dataSerialised))
            {
                return(null);
            }

            string headersSerialised;
            var    headers = new Dictionary <String, String>();

            if (HeadersDictionary.TryGetValue(correlationId, out headersSerialised))
            {
                headers = messageSerialiser.Deserialise <Dictionary <String, String> >(headersSerialised);
            }

            var sagaDataType = NSagaReflection.GetInterfaceGenericType <TSaga>(typeof(ISaga <>));

            var dataObject = messageSerialiser.Deserialise(dataSerialised, sagaDataType);

            var saga = sagaFactory.ResolveSaga <TSaga>();

            NSagaReflection.Set(saga, "SagaData", dataObject);
            NSagaReflection.Set(saga, "CorrelationId", correlationId);
            NSagaReflection.Set(saga, "Headers", headers);

            return(saga);
        }
 /// <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;
 }
예제 #5
0
        /// <summary>
        /// Deletes the saga instance from the storage
        /// </summary>
        /// <typeparam name="TSaga">Type of saga</typeparam>
        /// <param name="saga">Saga to be deleted</param>
        public void Complete <TSaga>(TSaga saga) where TSaga : class, IAccessibleSaga
        {
            Guard.ArgumentIsNotNull(saga, nameof(saga));

            var correlationId = (Guid)NSagaReflection.Get(saga, "CorrelationId");

            Complete(correlationId);
        }
        internal static void RegisterSagas(this TinyIoCContainer container, IEnumerable <Assembly> assembliesToScan)
        {
            var sagaTypePairs = NSagaReflection.GetAllSagasInterfaces(assembliesToScan);

            foreach (var sagaTypePair in sagaTypePairs)
            {
                container.Register(sagaTypePair.Value, sagaTypePair.Key);
            }
        }
예제 #7
0
        /// <summary>
        /// Returns a list of all the Saga classes in the provided assemblies.
        /// </summary>
        /// <param name="assemblies">The assemblies to scan</param>
        /// <returns>List of Types that implement <see cref="ISaga{TSagaData}"/></returns>
        public static IEnumerable <Type> GetAllSagaTypes(IEnumerable <Assembly> assemblies)
        {
            var allSagaTypes = assemblies.SelectMany(a => a.GetTypes())
                               .Where(t => NSagaReflection.TypeImplementsInterface(t, typeof(ISaga <>)))
                               .Where(t => t.IsClass)
                               .ToList();

            return(allSagaTypes);
        }
예제 #8
0
        /// <summary>
        /// Persists the instance of saga into the database storage.
        ///
        /// Actually stores SagaData and Headers. All other variables in saga are not persisted
        /// </summary>
        /// <typeparam name="TSaga">Type of saga</typeparam>
        /// <param name="saga">Saga instance</param>
        public void Save <TSaga>(TSaga saga) where TSaga : class, IAccessibleSaga
        {
            Guard.ArgumentIsNotNull(saga, nameof(saga));

            var sagaData      = NSagaReflection.Get(saga, "SagaData");
            var sagaHeaders   = (Dictionary <String, String>)NSagaReflection.Get(saga, "Headers");
            var correlationId = (Guid)NSagaReflection.Get(saga, "CorrelationId");

            var serialisedData = messageSerialiser.Serialise(sagaData);

            var dataModel = new SagaData()
            {
                CorrelationId = correlationId,
                BlobData      = serialisedData,
            };

            using (var connection = connectionFactory.CreateOpenConnection())
                using (var database = new Database(connection))
                    using (var transaction = database.GetTransaction())
                    {
                        try
                        {
                            int updatedRaws = database.Update(dataModel);

                            if (updatedRaws == 0)
                            {
                                // no records were updated - this means no records already exist - need to insert new record
                                database.Insert(dataModel);
                            }

                            // delete all existing headers
                            database.Delete <SagaHeaders>("WHERE CorrelationId=@0", correlationId);

                            // and insert updated ones
                            foreach (var header in sagaHeaders)
                            {
                                var storedHeader = new SagaHeaders()
                                {
                                    CorrelationId = correlationId,
                                    Key           = header.Key,
                                    Value         = header.Value,
                                };

                                database.Insert(storedHeader);
                            }
                            transaction.Complete();
                        }
                        catch (Exception)
                        {
                            transaction.Dispose();
                            throw;
                        }
                    }
        }
        /// <summary>
        /// Persists the instance of saga into the database storage.
        /// Actually stores SagaData and Headers. All other variables in saga are not persisted
        /// </summary>
        /// <typeparam name="TSaga">Type of saga</typeparam>
        /// <param name="saga">Saga instance</param>
        public void Save <TSaga>(TSaga saga) where TSaga : class, IAccessibleSaga
        {
            var sagaData      = NSagaReflection.Get(saga, "SagaData");
            var correlationId = (Guid)NSagaReflection.Get(saga, "CorrelationId");
            var headers       = (Dictionary <String, String>)NSagaReflection.Get(saga, "Headers");

            var serialisedData    = messageSerialiser.Serialise(sagaData);
            var serialisedHeaders = messageSerialiser.Serialise(headers);

            DataDictionary[correlationId]    = serialisedData;
            HeadersDictionary[correlationId] = serialisedHeaders;
        }
예제 #10
0
        /// <summary>
        /// Persists the instance of saga into the database storage.
        ///
        /// Actually stores SagaData and Headers. All other variables in saga are not persisted
        /// </summary>
        /// <typeparam name="TSaga">Type of saga</typeparam>
        /// <param name="saga">Saga instance</param>
        public void Save <TSaga>(TSaga saga) where TSaga : class, IAccessibleSaga
        {
            Guard.ArgumentIsNotNull(saga, nameof(saga));

            var sagaData      = NSagaReflection.Get(saga, "SagaData");
            var sagaHeaders   = saga.Headers;
            var correlationId = saga.CorrelationId;

            var serialisedData = messageSerialiser.Serialise(sagaData);

            var dataModel = new SagaData()
            {
                CorrelationId = correlationId,
                BlobData      = serialisedData,
            };

            using (var transaction = database.BeginTransaction())
            {
                try
                {
                    int updatedRaws = database.Update(dataModel);

                    if (updatedRaws == 0)
                    {
                        // no records were updated - this means no records already exist - need to insert new record
                        database.Insert(dataModel);
                    }

                    // delete all existing headers
                    database.DeleteById <SagaHeaders>(correlationId);

                    // and insert updated ones
                    foreach (var header in sagaHeaders)
                    {
                        var storedHeader = new SagaHeaders()
                        {
                            CorrelationId = correlationId,
                            Key           = header.Key,
                            Value         = header.Value,
                        };

                        database.Insert(storedHeader);
                    }
                    transaction.CommitTransaction();
                }
                catch (Exception ex)
                {
                    transaction.RollbackTransaction();
                    throw;
                }
            }
        }
        /// <summary>
        /// Consumes the specified initiating message and creates a new instance of the correlating saga.
        /// <para>Saga is not persisted if operation have failed</para>
        /// </summary>
        /// <param name="initiatingMessage">The initiating message.</param>
        /// <returns>
        /// Result of the operation
        /// </returns>
        /// <exception cref="System.ArgumentException"></exception>
        public OperationResult Consume(IInitiatingSagaMessage initiatingMessage)
        {
            Guard.CheckSagaMessage(initiatingMessage, nameof(initiatingMessage));

            var resolvedSaga = sagaFactory.ResolveSagaInititatedBy(initiatingMessage);
            var sagaType     = resolvedSaga.GetType();

            // try to find sagas that already exist
            var existingSaga = NSagaReflection.InvokeGenericMethod(sagaRepository, "Find", sagaType, initiatingMessage.CorrelationId);

            if (existingSaga != null)
            {
                throw new ArgumentException($"Trying to initiate the same saga twice. {initiatingMessage.GetType().Name} is Initiating Message, but saga of type {sagaType.Name} with CorrelationId {initiatingMessage.CorrelationId} already exists");
            }

            // now create an instance of saga and persist the data
            var saga = sagaFactory.ResolveSaga(sagaType);

            NSagaReflection.Set(saga, "CorrelationId", initiatingMessage.CorrelationId);

            // if SagaData is null - create an instance of the object and assign to saga
            var sagaData = NSagaReflection.Get(saga, "SagaData");

            if (sagaData == null)
            {
                var sagaDataType = NSagaReflection.GetInterfaceGenericType(saga, typeof(ISaga <>));
                var newSagaData  = Activator.CreateInstance(sagaDataType);
                NSagaReflection.Set(saga, "SagaData", newSagaData);
            }

            var sagaHeaders = NSagaReflection.Get(saga, "Headers");

            if (sagaHeaders == null)
            {
                NSagaReflection.Set(saga, "Headers", new Dictionary <String, String>());
            }

            pipelineHook.BeforeInitialisation(new PipelineContext(initiatingMessage, (IAccessibleSaga)saga));

            var errors = (OperationResult)NSagaReflection.InvokeMethod(saga, "Initiate", initiatingMessage);

            pipelineHook.AfterInitialisation(new PipelineContext(initiatingMessage, (IAccessibleSaga)saga, errors));

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

            return(errors);
        }
예제 #12
0
        /// <summary>
        /// Asserts that your sagas and messages are written correctly:
        /// <list type="bullet">
        /// <item><description>All classes impmenenting <see cref="ISaga{TSagaData}"/> should also have <see cref="InitiatedBy{TMsg}"/> applied.</description></item>
        /// <item><description>Any saga message can only be used by a single Saga. No message sharing between different sagas are allowed.</description></item>
        /// <item><description>All saga messages are used by sagas. No loose classes with <see cref="ISagaMessage"/> or <see cref="IInitiatingSagaMessage"/> without sagas consuming them are allowed.</description></item>
        /// </list>
        /// </summary>
        /// <exception cref="AggregateException">Aggregation of all the exceptions for each rule violation</exception>
        public void AssertConfigurationIsValid()
        {
            var allExceptions = new List <Exception>();

            sagaTypes = NSagaReflection.GetAllSagaTypes(assemblies);

            allExceptions.AddRange(AllSagasHaveInitialiser());

            allExceptions.AddRange(InitiatorMessagesAreNotShared());

            allExceptions.AddRange(ConsumerMessagesAreNotShared());


            if (allExceptions.Any())
            {
                throw new AggregateException(allExceptions);
            }
        }
예제 #13
0
        /// <summary>
        /// Returns a list of all the Saga classes in the provided assemblies.
        /// </summary>
        /// <param name="assemblies">The assemblies to scan</param>
        /// <returns>List of Types that implement <see cref="ISaga{TSagaData}"/></returns>
        public static IEnumerable <Type> GetAllSagaTypes(IEnumerable <Assembly> assemblies)
        {
            try
            {
                var types = assemblies.SelectMany(a => a.GetTypes()).ToList();

                var allSagaTypes = types
                                   .Where(t => NSagaReflection.TypeImplementsInterface(t, typeof(ISaga <>)))
                                   .Where(t => t.IsClass)
                                   .ToList();

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

                throw new Exception(BuildFusionException(ex));
            }
        }
예제 #14
0
        /// <summary>
        /// Finds and returns saga instance with the given correlation ID.
        /// You will get exceptions if TSaga does not match the actual saga data with the provided exception.
        ///
        /// Actually creates an instance of saga from service locator, retrieves SagaData and Headers from the storage and populates the instance with these.
        /// </summary>
        /// <typeparam name="TSaga">Type of saga we are looking for</typeparam>
        /// <param name="correlationId">CorrelationId to identify the saga</param>
        /// <returns>An instance of the saga. Or Null if there is no saga with this ID.</returns>
        public TSaga Find <TSaga>(Guid correlationId) where TSaga : class, IAccessibleSaga
        {
            Guard.ArgumentIsNotNull(correlationId, nameof(correlationId));

            var persistedData = database.GetById <SagaData>(correlationId).FirstOrDefault();

            if (persistedData == null)
            {
                return(null);
            }

            var sagaInstance = sagaFactory.ResolveSaga <TSaga>();
            var sagaDataType = NSagaReflection.GetInterfaceGenericType <TSaga>(typeof(ISaga <>));
            var sagaData     = messageSerialiser.Deserialise(persistedData.BlobData, sagaDataType);

            var headersPersisted = database.GetById <SagaHeaders>(correlationId);
            var headers          = headersPersisted.ToDictionary(k => k.Key, v => v.Value);

            sagaInstance.CorrelationId = correlationId;
            sagaInstance.Headers       = headers;
            NSagaReflection.Set(sagaInstance, "SagaData", sagaData);

            return(sagaInstance);
        }
        /// <summary>
        /// Deletes the saga instance from the storage
        /// </summary>
        /// <typeparam name="TSaga">Type of saga</typeparam>
        /// <param name="saga">Saga to be deleted</param>
        public void Complete <TSaga>(TSaga saga) where TSaga : class, IAccessibleSaga
        {
            var correlationId = (Guid)NSagaReflection.Get(saga, "CorrelationId");

            Complete(correlationId);
        }