Пример #1
0
        /// <summary>
        /// Creates a <see cref="SagaMetadata" /> from a specific Saga type.
        /// </summary>
        /// <param name="sagaType">A type representing a Saga. Must be a non-generic type inheriting from <see cref="Saga" />.</param>
        /// <param name="availableTypes">Additional available types, used to locate saga finders and other related classes.</param>
        /// <param name="conventions">Custom conventions to use while scanning types.</param>
        /// <returns>An instance of <see cref="SagaMetadata" /> describing the Saga.</returns>
        public static SagaMetadata Create(Type sagaType, IEnumerable <Type> availableTypes, Conventions conventions)
        {
            Guard.AgainstNull(nameof(sagaType), sagaType);
            Guard.AgainstNull(nameof(availableTypes), availableTypes);
            Guard.AgainstNull(nameof(conventions), conventions);
            if (!IsSagaType(sagaType))
            {
                throw new Exception(sagaType.FullName + " is not a saga");
            }

            var genericArguments = GetBaseSagaType(sagaType).GetGenericArguments();

            if (genericArguments.Length != 1)
            {
                throw new Exception($"'{sagaType.Name}' saga type does not implement Saga<T>");
            }

            var saga   = (Saga)FormatterServices.GetUninitializedObject(sagaType);
            var mapper = new SagaMapper();

            saga.ConfigureHowToFindSaga(mapper);

            var sagaEntityType = genericArguments.Single();

            ApplyScannedFinders(mapper, sagaEntityType, availableTypes, conventions);

            var finders = new List <SagaFinderDefinition>();


            var propertyMappings = mapper.Mappings.Where(m => !m.HasCustomFinderMap)
                                   .GroupBy(m => m.SagaPropName)
                                   .ToList();

            if (propertyMappings.Count > 1)
            {
                var messageTypes = string.Join(",", propertyMappings.SelectMany(g => g.Select(m => m.MessageType.FullName)).Distinct());
                throw new Exception($"Sagas can only have mappings that correlate on a single saga property. Use custom finders to correlate {messageTypes} to saga {sagaType.Name}");
            }

            CorrelationPropertyMetadata correlationProperty = null;

            if (propertyMappings.Any())
            {
                var mapping = propertyMappings.Single().First();
                correlationProperty = new CorrelationPropertyMetadata(mapping.SagaPropName, mapping.SagaPropType);
            }

            var associatedMessages = GetAssociatedMessages(sagaType)
                                     .ToList();

            foreach (var mapping in mapper.Mappings)
            {
                var associatedMessage = associatedMessages.FirstOrDefault(m => mapping.MessageType.IsAssignableFrom(m.MessageType));
                if (associatedMessage == null)
                {
                    var msgType = mapping.MessageType.Name;
                    if (mapping.HasCustomFinderMap)
                    {
                        throw new Exception($"Custom saga finder {mapping.CustomFinderType.FullName} maps message type {msgType} for saga {sagaType.Name}, but the saga does not handle that message. If {sagaType.Name} is supposed to handle this message, it should implement IAmStartedByMessages<{msgType}> or IHandleMessages<{msgType}>.");
                    }
                    throw new Exception($"Saga {sagaType.Name} contains a mapping for {msgType} in the ConfigureHowToFindSaga method, but does not handle that message. If {sagaType.Name} is supposed to handle this message, it should implement IAmStartedByMessages<{msgType}> or IHandleMessages<{msgType}>.");
                }
                SetFinderForMessage(mapping, sagaEntityType, finders);
            }

            foreach (var messageType in associatedMessages)
            {
                if (messageType.IsAllowedToStartSaga)
                {
                    var match = mapper.Mappings.FirstOrDefault(m => m.MessageType.IsAssignableFrom(messageType.MessageType));
                    if (match == null)
                    {
                        var simpleName = messageType.MessageType.Name;
                        throw new Exception($"Message type {simpleName} can start the saga {sagaType.Name} (the saga implements IAmStartedByMessages<{simpleName}>) but does not map that message to saga data. In the ConfigureHowToFindSaga method, add a mapping using:{Environment.NewLine}    mapper.ConfigureMapping<{simpleName}>(message => message.SomeMessageProperty).ToSaga(saga => saga.MatchingSagaProperty);");
                    }
                }
            }

            return(new SagaMetadata(sagaType.FullName, sagaType, sagaEntityType.FullName, sagaEntityType, correlationProperty, associatedMessages, finders));
        }
Пример #2
0
        /// <summary>
        /// Initializes a new instance of the <see cref="SagaMetadata" /> class.
        /// </summary>
        /// <param name="name">The name of the saga.</param>
        /// <param name="sagaType">The type for this saga.</param>
        /// <param name="entityName">The name of the saga data entity.</param>
        /// <param name="sagaEntityType">The type of the related saga entity.</param>
        /// <param name="correlationProperty">The property this saga is correlated on if any.</param>
        /// <param name="messages">The messages collection that a saga handles.</param>
        /// <param name="finders">The finder definition collection that can find this saga.</param>
        public SagaMetadata(string name, Type sagaType, string entityName, Type sagaEntityType, CorrelationPropertyMetadata correlationProperty, IReadOnlyCollection <SagaMessage> messages, IReadOnlyCollection <SagaFinderDefinition> finders)
        {
            this.correlationProperty = correlationProperty;
            Name           = name;
            EntityName     = entityName;
            SagaEntityType = sagaEntityType;
            SagaType       = sagaType;


            if (!messages.Any(m => m.IsAllowedToStartSaga))
            {
                throw new Exception($@"
Sagas must have at least one message that is allowed to start the saga. Add at least one `IAmStartedByMessages` to the {name} saga.");
            }

            if (correlationProperty != null)
            {
                if (!AllowedCorrelationPropertyTypes.Contains(correlationProperty.Type))
                {
                    var supportedTypes = string.Join(",", AllowedCorrelationPropertyTypes.Select(t => t.Name));

                    throw new Exception($@"
{correlationProperty.Type.Name} is not supported for correlated properties. Change the correlation property {correlationProperty.Name} on saga {name} to any of the supported types, {supportedTypes}, or use a custom saga finder.");
                }
            }

            associatedMessages = new Dictionary <string, SagaMessage>();

            foreach (var sagaMessage in messages)
            {
                associatedMessages[sagaMessage.MessageTypeName] = sagaMessage;
            }

            sagaFinders = new Dictionary <string, SagaFinderDefinition>();

            foreach (var finder in finders)
            {
                sagaFinders[finder.MessageTypeName] = finder;
            }
        }
Пример #3
0
        /// <summary>
        /// Property this saga is correlated on.
        /// </summary>
        public bool TryGetCorrelationProperty(out CorrelationPropertyMetadata property)
        {
            property = correlationProperty;

            return(property != null);
        }