public override async Task <IContainSagaData> Find(IServiceProvider builder, SagaFinderDefinition finderDefinition, SynchronizedStorageSession storageSession, ContextBag context, object message, IReadOnlyDictionary <string, string> messageHeaders, CancellationToken cancellationToken) { var headerName = (string)finderDefinition.Properties["message-header-name"]; if (!messageHeaders.TryGetValue(headerName, out var messageHeaderValue)) { var saga = context.Get <ActiveSagaInstance>(); var sagaEntityName = saga.Metadata.Name; var messageName = finderDefinition.MessageTypeName; throw new Exception($"Message {messageName} mapped to saga {sagaEntityName} is missing a header used for correlation: {headerName}."); } var correlationPropertyName = (string)finderDefinition.Properties["saga-property-name"]; var correlationPropertyType = (Type)finderDefinition.Properties["saga-property-type"]; object convertedHeaderValue; try { convertedHeaderValue = TypeDescriptor.GetConverter(correlationPropertyType).ConvertFromInvariantString(messageHeaderValue); } catch (Exception exception) { var saga = context.Get <ActiveSagaInstance>(); var sagaEntityName = saga.Metadata.Name; var messageName = finderDefinition.MessageTypeName; throw new Exception($"Message {messageName} mapped to saga {sagaEntityName} contains correlation header {headerName} value that cannot be cast to correlation property type {correlationPropertyType}: {messageHeaderValue}", exception); } var lookupValues = context.GetOrCreate <SagaLookupValues>(); lookupValues.Add <TSagaData>(correlationPropertyName, convertedHeaderValue); if (convertedHeaderValue == null) { var saga = context.Get <ActiveSagaInstance>(); var sagaEntityName = saga.Metadata.Name; var messageName = finderDefinition.MessageTypeName; throw new Exception($"Message {messageName} mapped to saga {sagaEntityName} has attempted to assign null to the correlation property {correlationPropertyName}. Correlation properties cannot be assigned null."); } if (correlationPropertyName.ToLower() == "id") { return(await persister.Get <TSagaData>((Guid)convertedHeaderValue, storageSession, context, cancellationToken).ConfigureAwait(false)); } return(await persister.Get <TSagaData>(correlationPropertyName, convertedHeaderValue, storageSession, context, cancellationToken).ConfigureAwait(false)); }
public async Task <TSagaData> Get <TSagaData>(Guid sagaId, SynchronizedStorageSession session, ContextBag context) where TSagaData : IContainSagaData { var operations = new SagaPersisterAtomicOperations(session); var incomingMessage = context.Get <IncomingMessage>(); var messageId = incomingMessage.MessageId; var indexStream = BuildSagaByIdStreamName(typeof(TSagaData), sagaId); var slice = await session.ReadStreamEventsBackwardAsync(indexStream, -1, 1, true).ConfigureAwait(false); if (slice.Status != SliceReadStatus.Success) { return(default(TSagaData)); } if (slice.Events[0].Event.EventType != SagaIndexEventType) { return(await operations.ReadSagaData <TSagaData>(indexStream, slice.Events[0], messageId)); } var indexEntry = slice.Events[0].Event.Data.ParseJson <SagaIndexEvent>(); var dataStream = indexEntry.DataStreamName; slice = await session.ReadStreamEventsBackwardAsync(dataStream, -1, 1, true).ConfigureAwait(false); if (slice.Status != SliceReadStatus.Success) { return(default(TSagaData)); } var sagaData = await operations.ReadSagaData <TSagaData>(dataStream, slice.Events[0], messageId); // ReSharper disable once CompareNonConstrainedGenericWithNull if (sagaData != null) { return(sagaData); } throw new Exception($"Either the saga has not yet been created successfully or saga ID {sagaId} has been sent out in a ghost message."); }
public async Task Complete(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context) { var sagas = await GetSagasDictionary().ConfigureAwait(false); var secondaryIndex = await GetIndexDicitonary().ConfigureAwait(false); using (var tx = stateManager.CreateTransaction()) { var sagaId = sagaData.Id; var correlationId = await GetCorrelationId(secondaryIndex, tx, sagaId).ConfigureAwait(false); await secondaryIndex.TryRemoveAsync(tx, sagaId).ConfigureAwait(false); var previouslyExistingState = await sagas.TryRemoveAsync(tx, correlationId).ConfigureAwait(false); if (previouslyExistingState.HasValue == false) { throw new Exception($"Failed to complete saga data for SagaId='{sagaId}' and CorrelationId='{correlationId}'"); } var dataAtReading = context.Get <Metadata>().SagaData[sagaId]; var dataAtDeletion = previouslyExistingState.Value; if (Compare(dataAtReading, dataAtDeletion) == false) { throw new Exception($"Optimistic concurrency failure for SagaId='{sagaId}' and CorrelationId='{correlationId}'"); } await tx.CommitAsync().ConfigureAwait(false); } }
public async Task Update(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context) { var sagas = await GetSagasDictionary().ConfigureAwait(false); var secondaryIndex = await GetIndexDicitonary().ConfigureAwait(false); using (var tx = stateManager.CreateTransaction()) { var sagaId = sagaData.Id; var correlationId = await GetCorrelationId(secondaryIndex, tx, sagaId).ConfigureAwait(false); var data = Serialize(sagaData); var previousData = context.Get <Metadata>().SagaData[sagaData.Id]; var result = await sagas.TryUpdateAsync(tx, correlationId, data, previousData); if (result == false) { throw new Exception($"Optimistic concurrency failure for SagaId='{sagaData.Id}' and CorrelationId='{sagaId}'"); } await tx.CommitAsync(); } }
public Task <OutboxTransaction> BeginTransaction(ContextBag context) { var connection = GetConnection(context); var messageId = context.Get <IncomingMessage>().MessageId; return(Task.FromResult <OutboxTransaction>(new EventStoreOutboxTransaction(GetStreamName(messageId), messageId, connection))); }
public override async Task <IContainSagaData> Find(IServiceProvider builder, SagaFinderDefinition finderDefinition, SynchronizedStorageSession storageSession, ContextBag context, object message, IReadOnlyDictionary <string, string> messageHeaders, CancellationToken cancellationToken = default) { var propertyAccessor = (Func <object, object>)finderDefinition.Properties["property-accessor"]; var propertyValue = propertyAccessor(message); var sagaPropertyName = (string)finderDefinition.Properties["saga-property-name"]; var lookupValues = context.GetOrCreate <SagaLookupValues>(); lookupValues.Add <TSagaData>(sagaPropertyName, propertyValue); if (propertyValue == null) { var saga = context.Get <ActiveSagaInstance>(); var sagaEntityName = saga.Metadata.Name; var messageName = finderDefinition.MessageTypeName; throw new Exception($"Message {messageName} mapped to saga {sagaEntityName} has attempted to assign null to the correlation property {sagaPropertyName}. Correlation properties cannot be assigned null."); } if (sagaPropertyName.ToLower() == "id") { return(await sagaPersister.Get <TSagaData>((Guid)propertyValue, storageSession, context, cancellationToken).ConfigureAwait(false)); } return(await sagaPersister.Get <TSagaData>(sagaPropertyName, propertyValue, storageSession, context, cancellationToken).ConfigureAwait(false)); }
public Task <CompletableSynchronizedStorageSession> OpenSession(ContextBag context) { var message = context.Get <IncomingMessage>(); var session = sessionCreator.OpenSession(message.Headers); var synchronizedStorageSession = new RavenDBSynchronizedStorageSession(session, true); return(Task.FromResult((CompletableSynchronizedStorageSession)synchronizedStorageSession)); }
public static Type GetSagaType(this ContextBag context) { var activeSagaInstance = context.Get <ActiveSagaInstance>(); if (activeSagaInstance != null) { return(activeSagaInstance.Instance.GetType()); } throw new Exception($"Expected to find an instance of {nameof(ActiveSagaInstance)} in the {nameof(ContextBag)}."); }
public Task <CompletableSynchronizedStorageSession> OpenSession(ContextBag context, CancellationToken cancellationToken = default) { var message = context.Get <IncomingMessage>(); var session = sessionCreator.OpenSession(message.Headers); var synchronizedStorageSession = new RavenDBSynchronizedStorageSession(session, context, true); sessionHolder?.SetCurrentSession(session); return(Task.FromResult((CompletableSynchronizedStorageSession)synchronizedStorageSession)); }
public Task Update(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context) { // store the schema version in case it has changed var container = context.Get <SagaDataContainer>($"{SagaContainerContextKeyPrefix}{sagaData.Id}"); var documentSession = session.RavenSession(); documentSession.StoreSchemaVersionInMetadata(container); // dirty tracking will do the rest for us return(Task.CompletedTask); }
public Task Complete(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context) { var documentSession = session.RavenSession(); var container = context.Get <SagaDataContainer>($"{SagaContainerContextKeyPrefix}{sagaData.Id}"); documentSession.Delete(container); if (container.IdentityDocId != null) { documentSession.Advanced.Defer(new DeleteCommandData(container.IdentityDocId, null)); } return(Task.CompletedTask); }
public async Task <TSagaData> Get <TSagaData>(string propertyName, object propertyValue, SynchronizedStorageSession session, ContextBag context) where TSagaData : IContainSagaData { var operations = new SagaPersisterAtomicOperations(session); var incomingMessage = context.Get <IncomingMessage>(); var messageId = incomingMessage.MessageId; var streamName = BuildSagaDataStreamName(typeof(TSagaData), propertyValue); var slice = await session.ReadStreamEventsBackwardAsync(streamName, -1, 1, true).ConfigureAwait(false); if (slice.Status != SliceReadStatus.Success) { return(default(TSagaData)); } return(await operations.ReadSagaData <TSagaData>(streamName, slice.Events[0], messageId)); }
IEventStoreConnection GetConnection(ContextBag context) { if (outboxConnection != null) { return(outboxConnection); } var transportTransaction = context.Get <TransportTransaction>(); IEventStoreConnection connection; if (!transportTransaction.TryGet(out connection)) { throw new Exception("EventStore persistence can only be used either with EventStore transport or requires explicitly."); } return(connection); }
public async Task SetAsDispatched(string messageId, ContextBag context, CancellationToken cancellationToken = default) { var setAsDispatchedHolder = context.Get <SetAsDispatchedHolder>(); var partitionKey = setAsDispatchedHolder.PartitionKey; var containerHolder = setAsDispatchedHolder.ContainerHolder; var operation = new OutboxDelete(new OutboxRecord { Id = messageId, Dispatched = true }, partitionKey, serializer, ttlInSeconds, context); var transactionalBatch = containerHolder.Container.CreateTransactionalBatch(partitionKey); await transactionalBatch.ExecuteOperationAsync(operation, containerHolder.PartitionKeyPath, cancellationToken : cancellationToken).ConfigureAwait(false); }
public async Task <T> Get <T>(Guid sagaId, SynchronizedStorageSession session, ContextBag context, CancellationToken cancellationToken = default) where T : class, IContainSagaData { var documentSession = session.RavenSession(); var docId = DocumentIdForSagaData(documentSession, typeof(T), sagaId); if (enablePessimisticLocking) { var index = await AcquireLease(documentSession.Advanced.DocumentStore, docId, cancellationToken).ConfigureAwait(false); // only true if we always have synchronized storage session around which is a valid assumption context.Get <SagaDataLeaseHolder>().DocumentsIdsAndIndexes.Add((docId, index)); } var container = await documentSession.LoadAsync <SagaDataContainer>(docId, cancellationToken).ConfigureAwait(false); if (container == null) { return(default);
public Task <OutboxTransaction> BeginTransaction(ContextBag context) { return(Task.FromResult <OutboxTransaction>(new StupidTransaction(context.Get <IOutboxActorService>()))); }