/// <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); }
public IAccessibleSaga ResolveSagaConsumedBy(ISagaMessage message) { var interfaceType = typeof(ConsumerOf <>).MakeGenericType(message.GetType()); var saga = container.Resolve(interfaceType); return((IAccessibleSaga)saga); }
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); }
/// <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)); } }
/// <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)); }
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"); } }
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); }
/// <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); }
public IAccessibleSaga ResolveSagaConsumedBy(ISagaMessage message) { throw new NotImplementedException(); }
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); }
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); }
/// <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; }
/// <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; }