public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { List <TSaga> sagaInstances = await _collection.Find(context.Query.FilterExpression).ToListAsync().ConfigureAwait(false); if (!sagaInstances.Any()) { var missingPipe = new MissingPipe <TSaga, T>(_collection, next, _mongoDbSagaConsumeContextFactory); await policy.Missing(context, missingPipe).ConfigureAwait(false); } else { foreach (var instance in sagaInstances) { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } } } catch (SagaException sex) { context.LogFault(sex); throw; } catch (Exception ex) { context.LogFault(ex); throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } }
public async Task Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } TSaga instance; if (policy.PreInsertInstance(context, out instance)) { await PreInsertSagaInstance(context, instance).ConfigureAwait(false); } if (instance == null) { instance = await _collection.Find(x => x.CorrelationId == context.CorrelationId).SingleOrDefaultAsync(context.CancellationToken).ConfigureAwait(false); } if (instance == null) { var missingSagaPipe = new MissingPipe <TSaga, T>(_collection, next, _mongoDbSagaConsumeContextFactory); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var session = _sessionFactory.OpenSession()) using (var transaction = session.BeginTransaction()) { try { IList <TSaga> instances = await session.QueryOver <TSaga>() .Where(context.Query.FilterExpression) .ListAsync <TSaga>() .ConfigureAwait(false); if (instances.Count == 0) { var missingSagaPipe = new MissingPipe <T>(session, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await Task.WhenAll(instances.Select(instance => SendToInstance(context, policy, instance, next, session))).ConfigureAwait(false); } // TODO partial failure should not affect them all if (transaction.IsActive) { await transaction.CommitAsync().ConfigureAwait(false); } } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } if (transaction.IsActive) { await transaction.RollbackAsync().ConfigureAwait(false); } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } if (transaction.IsActive) { await transaction.RollbackAsync().ConfigureAwait(false); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } }
public async Task Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var sagaId = context.CorrelationId.Value; if (policy.PreInsertInstance(context, out var instance)) { await PreInsertSagaInstance <T>(instance, context.MessageId).ConfigureAwait(false); } if (instance == null) { instance = await GetSaga(sagaId); } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(_connection, _options, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } Guid sagaId = context.CorrelationId.Value; using (DbContext dbContext = _sagaDbContextFactory()) using (var transaction = dbContext.Database.BeginTransaction(_isolationLevel)) { bool inserted = false; TSaga instance; if (policy.PreInsertInstance(context, out instance)) { inserted = await PreInsertSagaInstance <T>(dbContext, instance, context.CancellationToken).ConfigureAwait(false); } try { if (instance == null) { instance = dbContext.Set <TSaga>().SingleOrDefault(x => x.CorrelationId == sagaId); } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(dbContext, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Used {2}", TypeMetadataCache <TSaga> .ShortName, instance.CorrelationId, TypeMetadataCache <T> .ShortName); } var sagaConsumeContext = new EntityFrameworkSagaConsumeContext <TSaga, T>(dbContext, context, instance); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); // if (inserted && !sagaConsumeContext.IsCompleted) // dbContext.Set<TSaga>().Update(instance); } await dbContext.SaveChangesAsync().ConfigureAwait(false); transaction.Commit(); } catch (Exception) { transaction.Rollback(); throw; } } }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var sagaId = context.CorrelationId.Value; using (var session = _store.DirtyTrackedSession()) { TSaga instance; if (policy.PreInsertInstance(context, out instance)) { PreInsertSagaInstance <T>(session, instance); } if (instance == null) { instance = session.Load <TSaga>(sagaId); } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(session, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, instance, next, session).ConfigureAwait(false); } } }
public void GivenAMissingPipe() { _probeContext = new Mock <ProbeContext>(); _nextPipe = new Mock <IPipe <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> > >(); _pipe = new MissingPipe <SimpleSaga, InitiateSimpleSaga>(Mock.Of <IMongoCollection <SimpleSaga> >(), _nextPipe.Object, Mock.Of <IMongoDbSagaConsumeContextFactory>()); }
public void GivenAMissingPipe() { _probeContext = new Mock <ProbeContext>(); _nextPipe = new Mock <IPipe <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> > >(); _pipe = new MissingPipe <SimpleSaga, InitiateSimpleSaga>(Mock.Of <IDocumentClient>(), SagaRepository.DatabaseName, SagaRepository.CollectionName, _nextPipe.Object, Mock.Of <IDocumentDbSagaConsumeContextFactory>(), new RequestOptions()); }
public void GivenAMissingPipe() { _probeContext = new Mock<ProbeContext>(); _nextPipe = new Mock<IPipe<SagaConsumeContext<SimpleSaga, InitiateSimpleSaga>>>(); _pipe = new MissingPipe<SimpleSaga, InitiateSimpleSaga>(Mock.Of<IMongoCollection<SimpleSaga>>(), _nextPipe.Object, Mock.Of<IMongoDbSagaConsumeContextFactory>()); }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var sagaId = context.CorrelationId.Value; var needToLeaveSagas = true; await _sagas.MarkInUse(context.CancellationToken).ConfigureAwait(false); try { SagaInstance <TSaga> saga = _sagas[sagaId]; if (saga == null) { var missingSagaPipe = new MissingPipe <T>(this, next, true); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); _sagas.Release(); needToLeaveSagas = false; } else { await saga.MarkInUse(context.CancellationToken).ConfigureAwait(false); try { _sagas.Release(); needToLeaveSagas = false; if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Used {2}", TypeMetadataCache <TSaga> .ShortName, sagaId, TypeMetadataCache <T> .ShortName); } SagaConsumeContext <TSaga, T> sagaConsumeContext = new InMemorySagaConsumeContext <TSaga, T>(context, saga.Instance, () => Remove(saga, context.CancellationToken)); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); } finally { saga.Release(); } } } finally { if (needToLeaveSagas) { _sagas.Release(); } } }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var correlationId = context.CorrelationId.Value; if (policy.PreInsertInstance(context, out TSaga preInsertInstance)) { using (var connection = new SqlConnection(_connectionString)) { await PreInsertSagaInstance <T>(connection, context, preInsertInstance).ConfigureAwait(false); } } using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) using (var connection = new SqlConnection(_connectionString)) { try { var tableName = GetTableName(); var instance = await connection.QuerySingleOrDefaultAsync <TSaga>( $"SELECT * FROM {tableName} WITH (UPDLOCK, ROWLOCK) WHERE CorrelationId = @correlationId", new { correlationId }).ConfigureAwait(false); if (instance == null) { var missingSagaPipe = new MissingPipe <T>(connection, tableName, next, InsertSagaInstance); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, connection, policy, instance, tableName, next).ConfigureAwait(false); } transaction.Complete(); } catch (SagaException sex) { LogContext.Error?.Log(sex, "SAGA:{SagaType} Exception {MessageType}", TypeMetadataCache <TSaga> .ShortName, TypeMetadataCache <T> .ShortName); throw; } catch (Exception ex) { LogContext.Error?.Log(ex, "SAGA:{SagaType} Exception {MessageType}", TypeMetadataCache <TSaga> .ShortName, TypeMetadataCache <T> .ShortName); throw new SagaException(ex.Message, typeof(TSaga), typeof(T), correlationId, ex); } } }
Task ISagaRepository <TSaga> .SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { SagaInstance <TSaga>[] existingSagas = _sagas.Where(context.Query).ToArray(); if (existingSagas.Length == 0) { var missingSagaPipe = new MissingPipe <T>(this, next); return(policy.Missing(context, missingSagaPipe)); } return(Task.WhenAll(existingSagas.Select(instance => SendToInstance(context, policy, instance, next)))); }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var sagaId = context.CorrelationId.Value; var db = _redisDbFactory(); ITypedDatabase <TSaga> sagas = db.As <TSaga>(); IAsyncDisposable pessimisticLock = null; if (!_optimistic) { pessimisticLock = await db.AcquireLockAsync(sagaId, _lockTimeout, _lockRetryTimeout).ConfigureAwait(false); } try { if (policy.PreInsertInstance(context, out var instance)) { await PreInsertSagaInstance <T>(sagas, instance).ConfigureAwait(false); } if (instance == null) { instance = await sagas.Get(sagaId).ConfigureAwait(false); } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(sagas, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(sagas, context, policy, next, instance).ConfigureAwait(false); } } finally { if (!_optimistic) { await pessimisticLock.DisposeAsync().ConfigureAwait(false); } } }
public void GivenAMissingPipe_WhenSendingAndProxyCompleted() { _nextPipe = new Mock <IPipe <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> > >(); _proxy = new Mock <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> >(); _proxy.SetupGet(m => m.IsCompleted).Returns(true); _consumeContextFactory = new Mock <IDocumentDbSagaConsumeContextFactory>(); _mockDocumentClient = new Mock <IDocumentClient>(); _context = new Mock <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> >(); _consumeContextFactory.Setup(m => m.Create(_mockDocumentClient.Object, "", "", _context.Object, It.IsAny <SimpleSaga>(), false, null)).Returns(_proxy.Object); _pipe = new MissingPipe <SimpleSaga, InitiateSimpleSaga>(_mockDocumentClient.Object, "", "", _nextPipe.Object, _consumeContextFactory.Object, null); TaskUtil.Await(() => _pipe.Send(_context.Object)); }
public void GivenAMissingPipe_WhenSendingAndProxyCompleted() { _collection = new Mock <IMongoCollection <SimpleSaga> >(); _nextPipe = new Mock <IPipe <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> > >(); _proxy = new Mock <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> >(); _proxy.SetupGet(m => m.IsCompleted).Returns(true); _consumeContextFactory = new Mock <IMongoDbSagaConsumeContextFactory>(); _context = new Mock <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> >(); _consumeContextFactory.Setup(m => m.Create(_collection.Object, _context.Object, _context.Object.Saga, false)).Returns(_proxy.Object); _pipe = new MissingPipe <SimpleSaga, InitiateSimpleSaga>(_collection.Object, _nextPipe.Object, _consumeContextFactory.Object); TaskUtil.Await(() => _pipe.Send(_context.Object)); }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) using (var connection = new SqlConnection(_connectionString)) { try { var tableName = GetTableName <T>(); var(whereStatement, parameters) = WhereStatementHelper.GetWhereStatementAndParametersFromExpression(context.Query.FilterExpression); List <TSaga> instances = (await connection.QueryAsync <TSaga>($"SELECT * FROM {tableName} WITH (UPDLOCK, ROWLOCK) {whereStatement}", parameters).ConfigureAwait(false)).ToList(); if (!instances.Any()) { var missingSagaPipe = new MissingPipe <T>(connection, tableName, next, InsertSagaInstance); await policy.Missing(context, missingSagaPipe); } else { foreach (var instance in instances) { await SendToInstance(context, connection, policy, instance, tableName, next).ConfigureAwait(false); } } transaction.Complete(); } catch (SagaException sex) { if (Log.IsErrorEnabled) { Log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } throw; } catch (Exception ex) { if (Log.IsErrorEnabled) { Log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } }
public void GivenAMissingPipe_WhenSendingAndProxyCompleted() { _collection = new Mock<IMongoCollection<SimpleSaga>>(); _nextPipe = new Mock<IPipe<SagaConsumeContext<SimpleSaga, InitiateSimpleSaga>>>(); _proxy = new Mock<SagaConsumeContext<SimpleSaga, InitiateSimpleSaga>>(); _proxy.SetupGet(m => m.IsCompleted).Returns(true); _consumeContextFactory = new Mock<IMongoDbSagaConsumeContextFactory>(); _context = new Mock<SagaConsumeContext<SimpleSaga, InitiateSimpleSaga>>(); _consumeContextFactory.Setup(m => m.Create(_collection.Object, _context.Object, _context.Object.Saga, false)).Returns(_proxy.Object); _pipe = new MissingPipe<SimpleSaga, InitiateSimpleSaga>(_collection.Object, _nextPipe.Object, _consumeContextFactory.Object); TaskUtil.Await(() => _pipe.Send(_context.Object)); }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { if (!context.TryGetPayload(out MessageSessionContext sessionContext)) { throw new SagaException($"The session-based saga repository requires an active message session: {TypeMetadataCache<TSaga>.ShortName}", typeof(TSaga), typeof(T)); } if (Guid.TryParse(sessionContext.SessionId, out var sessionId)) { context = new CorrelationIdConsumeContextProxy <T>(context, sessionId); } StartedActivity?activity = LogContext.IfEnabled(OperationName.Saga.Send)?.StartSagaActivity <TSaga, T>(context); try { var saga = await ReadSagaState(sessionContext).ConfigureAwait(false); if (saga == null) { var missingSagaPipe = new MissingPipe <T>(next, WriteSagaState); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { SagaConsumeContext <TSaga, T> sagaConsumeContext = new MessageSessionSagaConsumeContext <TSaga, T>(context, sessionContext, saga); LogContext.Debug?.Log("SAGA:{SagaType}:{CorrelationId} Used {MessageType}", TypeMetadataCache <TSaga> .ShortName, context.CorrelationId, TypeMetadataCache <T> .ShortName); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); if (!sagaConsumeContext.IsCompleted) { await WriteSagaState(sessionContext, saga).ConfigureAwait(false); LogContext.Debug?.Log("SAGA:{SagaType}:{CorrelationId} Updated {MessageType}", TypeMetadataCache <TSaga> .ShortName, context.CorrelationId, TypeMetadataCache <T> .ShortName); } } } finally { activity?.Stop(); } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var session = OpenSession()) { try { var guids = (await Find(context.Query).ConfigureAwait(false)).ToArray(); if (!guids.Any()) { var missingSagaPipe = new MissingPipe <T>(session, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { var ids = guids.Select(x => ConvertToSagaId(session, x)).ToArray(); var instances = await session.LoadAsync <TSaga>(ids); foreach (var instance in instances) { await SendToInstance(context, policy, instance, next, session).ConfigureAwait(false); } } await session.SaveChangesAsync(); } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error("Saga Exception Occurred", sex); } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error( $"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { List <TSaga> sagaInstances = (await _client .CreateDocumentQuery <TSaga>(UriFactory.CreateDocumentCollectionUri(_databaseName, _collectionName), _feedOptions) .Where(context.Query.FilterExpression).QueryAsync().ConfigureAwait(false)).ToList(); if (!sagaInstances.Any()) { var missingPipe = new MissingPipe <TSaga, T>(_client, _databaseName, _collectionName, next, _documentDbSagaConsumeContextFactory, _requestOptions); await policy.Missing(context, missingPipe).ConfigureAwait(false); } else { foreach (var instance in sagaInstances) { // To support optimistic concurrency, we need the Document ETag // So we will re-query to get the document, this should be fast because we are querying by Id var response = await _client .ReadDocumentAsync(UriFactory.CreateDocumentUri(_databaseName, _collectionName, instance.CorrelationId.ToString()), _requestOptions) .ConfigureAwait(false); await SendToInstance(context, policy, next, instance, response.Resource).ConfigureAwait(false); } } } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } }
public async Task Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } Document document = null; if (policy.PreInsertInstance(context, out var instance)) { document = await PreInsertSagaInstance(context, instance).ConfigureAwait(false); } if (instance == null) { try { ResourceResponse <Document> response = await _client .ReadDocumentAsync(UriFactory.CreateDocumentUri(_databaseName, _collectionName, context.CorrelationId.ToString()), _requestOptions) .ConfigureAwait(false); document = response.Resource; } catch (DocumentClientException e) when(e.StatusCode == System.Net.HttpStatusCode.NotFound) { // Couldn't find the document, swallowing exception } if (document != null) { instance = JsonConvert.DeserializeObject <TSaga>(document.ToString()); } } if (instance == null) { var missingSagaPipe = new MissingPipe <TSaga, T>(_client, _databaseName, _collectionName, next, _documentDbSagaConsumeContextFactory, _requestOptions); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, next, instance, document).ConfigureAwait(false); } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var dbContext = _sagaDbContextFactory()) using (var transaction = dbContext.Database.BeginTransaction(_isolationLevel)) { try { var sagaInstances = await dbContext.Set <TSaga>().Where(context.Query.FilterExpression).ToListAsync().ConfigureAwait(false); if (sagaInstances.Count == 0) { var missingSagaPipe = new MissingPipe <T>(dbContext, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { foreach (var instance in sagaInstances) { await SendToInstance(context, dbContext, policy, instance, next).ConfigureAwait(false); } } await dbContext.SaveChangesAsync().ConfigureAwait(false); transaction.Commit(); } catch (SagaException sex) { transaction.Rollback(); if (_log.IsErrorEnabled) { _log.Error("Saga Exception Occurred", sex); } } catch (Exception ex) { transaction.Rollback(); if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { MessageSessionContext sessionContext; if (!context.TryGetPayload(out sessionContext)) { throw new SagaException($"The session-based saga repository requires an active message session: {TypeMetadataCache<TSaga>.ShortName}", typeof(TSaga), typeof(T)); } Guid sessionId; if (Guid.TryParse(sessionContext.SessionId, out sessionId)) { context = new CorrelationIdConsumeContextProxy <T>(context, sessionId); } var saga = await ReadSagaState(sessionContext).ConfigureAwait(false); if (saga == null) { var missingSagaPipe = new MissingPipe <T>(next, WriteSagaState); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { SagaConsumeContext <TSaga, T> sagaConsumeContext = new MessageSessionSagaConsumeContext <TSaga, T>(context, sessionContext, saga); if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Existing {2}", TypeMetadataCache <TSaga> .ShortName, sessionContext.SessionId, TypeMetadataCache <T> .ShortName); } await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); if (!sagaConsumeContext.IsCompleted) { await WriteSagaState(sessionContext, saga).ConfigureAwait(false); if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Updated {2}", TypeMetadataCache <TSaga> .ShortName, sessionContext.SessionId, TypeMetadataCache <T> .ShortName); } } } }
public void GivenAMissingPipe_WhenSendingAndProxyIncomplete() { IMongoCollection<SimpleSaga> collection = SagaRepository.Instance.GetCollection<SimpleSaga>("sagas"); _nextPipe = new Mock<IPipe<SagaConsumeContext<SimpleSaga, InitiateSimpleSaga>>>(); _proxy = new Mock<SagaConsumeContext<SimpleSaga, InitiateSimpleSaga>>(); _proxy.SetupGet(m => m.IsCompleted).Returns(false); _consumeContextFactory = new Mock<IMongoDbSagaConsumeContextFactory>(); _saga = new SimpleSaga {CorrelationId = Guid.NewGuid()}; _context = new Mock<SagaConsumeContext<SimpleSaga, InitiateSimpleSaga>>(); _context.SetupGet(m => m.Saga).Returns(_saga); _consumeContextFactory.Setup(m => m.Create(collection, _context.Object, _context.Object.Saga, false)).Returns(_proxy.Object); _pipe = new MissingPipe<SimpleSaga, InitiateSimpleSaga>(collection, _nextPipe.Object, _consumeContextFactory.Object); TaskUtil.Await(() => _pipe.Send(_context.Object)); }
public async Task GivenAMissingPipe_WhenSendingAndProxyIncomplete() { _nextPipe = new Mock <IPipe <SagaConsumeContext <SimpleSagaResource, InitiateSimpleSaga> > >(); _proxy = new Mock <SagaConsumeContext <SimpleSagaResource, InitiateSimpleSaga> >(); _proxy.SetupGet(m => m.IsCompleted).Returns(false); _consumeContextFactory = new Mock <IDocumentDbSagaConsumeContextFactory>(); _saga = new SimpleSagaResource { CorrelationId = Guid.NewGuid() }; _context = new Mock <SagaConsumeContext <SimpleSagaResource, InitiateSimpleSaga> >(); _context.SetupGet(m => m.Saga).Returns(_saga); _consumeContextFactory.Setup(m => m.Create(SagaRepository.Instance.Client, SagaRepository.DatabaseName, SagaRepository.CollectionName, _context.Object, It.IsAny <SimpleSagaResource>(), false, null)).Returns(_proxy.Object); _pipe = new MissingPipe <SimpleSagaResource, InitiateSimpleSaga>(SagaRepository.Instance.Client, SagaRepository.DatabaseName, SagaRepository.CollectionName, _nextPipe.Object, _consumeContextFactory.Object, null); await _pipe.Send(_context.Object); }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var session = _store.DirtyTrackedSession()) { try { IEnumerable <TSaga> instances = await session.Query <TSaga>() .Where(context.Query.FilterExpression) .ToListAsync().ConfigureAwait(false); if (!instances.Any()) { var missingSagaPipe = new MissingPipe <T>(session, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { foreach (var instance in instances) { await SendToInstance(context, policy, instance, next, session).ConfigureAwait(false); } } } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } }
public async Task GivenAMissingPipe_WhenSendingAndProxyIncomplete() { var collection = SagaRepository.Instance.GetCollection <SimpleSaga>("sagas"); _nextPipe = new Mock <IPipe <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> > >(); _proxy = new Mock <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> >(); _proxy.SetupGet(m => m.IsCompleted).Returns(false); _consumeContextFactory = new Mock <IMongoDbSagaConsumeContextFactory>(); _saga = new SimpleSaga { CorrelationId = Guid.NewGuid() }; _context = new Mock <SagaConsumeContext <SimpleSaga, InitiateSimpleSaga> >(); _context.SetupGet(m => m.Saga).Returns(_saga); _consumeContextFactory.Setup(m => m.Create(collection, _context.Object, _context.Object.Saga, false)).Returns(_proxy.Object); _pipe = new MissingPipe <SimpleSaga, InitiateSimpleSaga>(collection, _nextPipe.Object, _consumeContextFactory.Object); await _pipe.Send(_context.Object); }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { var sagaInstances = (await _client .CreateDocumentQuery <TSaga>(UriFactory.CreateDocumentCollectionUri(_databaseName, _collectionName), _feedOptions) .Where(context.Query.FilterExpression).QueryAsync().ConfigureAwait(false)).ToList(); if (!sagaInstances.Any()) { var missingPipe = new MissingPipe <TSaga, T>(_client, _databaseName, _collectionName, next, _documentDbSagaConsumeContextFactory, _jsonSerializerSettings); await policy.Missing(context, missingPipe).ConfigureAwait(false); } else { foreach (var instance in sagaInstances) { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } } } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } }
public async Task Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var sagaId = context.CorrelationId.Value; var db = _redisConnection.GetDatabase(); TSaga instance; ITypedDatabase <TSaga> sagas = db.As <TSaga>(); if (policy.PreInsertInstance(context, out instance)) { await PreInsertSagaInstance <T>(sagas, instance).ConfigureAwait(false); } if (instance == null) { using (var distLock = _lockFactory.Create($"redislock:{context.CorrelationId.Value}", _expiry, _wait, _retryTime)) { if (distLock.IsAcquired) { instance = await sagas.Get(sagaId, _redisPrefix).ConfigureAwait(false); } } } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(db, _lockFactory, next, _redisPrefix); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { List <TSaga> sagaInstances = await _collection.Find(context.Query.FilterExpression).ToListAsync().ConfigureAwait(false); if (!sagaInstances.Any()) { var missingPipe = new MissingPipe <TSaga, T>(_collection, next, _mongoDbSagaConsumeContextFactory); await policy.Missing(context, missingPipe).ConfigureAwait(false); } else { foreach (var instance in sagaInstances) { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } } } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error("Saga Exception Occurred", sex); } } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } }
public async Task Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var sagaId = context.CorrelationId.Value; TSaga instance; using (var redis = _clientsManager.GetClient()) { var sagas = redis.As <TSaga>(); if (policy.PreInsertInstance(context, out instance)) { await PreInsertSagaInstance <T>(sagas, instance).ConfigureAwait(false); } if (instance == null) { instance = sagas.GetById(sagaId); } } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(_clientsManager, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var sagaId = context.CorrelationId.Value; using (var dbContext = _sagaDbContextFactory()) using (var transaction = dbContext.Database.BeginTransaction(_isolationLevel)) { // Hack for locking row for the duration of the transaction. var tableName = ((IObjectContextAdapter)dbContext).ObjectContext.CreateObjectSet <TSaga>().EntitySet.Name; await dbContext.Database.ExecuteSqlCommandAsync($"select 1 from {tableName} WITH (UPDLOCK, ROWLOCK) WHERE CorrelationId = @p0", sagaId) .ConfigureAwait(false); var inserted = false; TSaga instance; if (policy.PreInsertInstance(context, out instance)) { inserted = await PreInsertSagaInstance <T>(dbContext, instance, context.CancellationToken).ConfigureAwait(false); } try { if (instance == null) { instance = dbContext.Set <TSaga>().SingleOrDefault(x => x.CorrelationId == sagaId); } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(dbContext, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Used {2}", TypeMetadataCache <TSaga> .ShortName, instance.CorrelationId, TypeMetadataCache <T> .ShortName); } var sagaConsumeContext = new EntityFrameworkSagaConsumeContext <TSaga, T>(dbContext, context, instance); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); } await dbContext.SaveChangesAsync().ConfigureAwait(false); transaction.Commit(); } catch (DbUpdateException ex) { var baseException = ex.GetBaseException() as SqlException; if (baseException != null && baseException.Number == 1205) { // deadlock, no need to rollback } else { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } } throw; } catch (Exception) { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } throw; } } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var dbContext = _sagaDbContextFactory()) { // We just get the correlation ids related to our Filter. // We do this outside of the transaction to make sure we don't create a range lock. var correlationIds = await dbContext.Set <TSaga>().Where(context.Query.FilterExpression) .Select(x => x.CorrelationId) .ToListAsync() .ConfigureAwait(false); using (var transaction = dbContext.Database.BeginTransaction(_isolationLevel)) { try { if (correlationIds.Count == 0) { var missingSagaPipe = new MissingPipe <T>(dbContext, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { var tableName = ((IObjectContextAdapter)dbContext).ObjectContext.CreateObjectSet <TSaga>().EntitySet.Name; foreach (var correlationId in correlationIds) { // Hack for locking row for the duration of the transaction. // We only lock one at a time, since we don't want an accidental range lock. await dbContext.Database.ExecuteSqlCommandAsync( $"select 2 from {tableName} WITH (UPDLOCK, ROWLOCK) WHERE CorrelationId = @p0", correlationId).ConfigureAwait(false); var instance = dbContext.Set <TSaga>().SingleOrDefault(x => x.CorrelationId == correlationId); await SendToInstance(context, dbContext, policy, instance, next).ConfigureAwait(false); } } await dbContext.SaveChangesAsync().ConfigureAwait(false); transaction.Commit(); } catch (DbUpdateException ex) { var baseException = ex.GetBaseException() as SqlException; if (baseException != null && baseException.Number == 1205) { // deadlock, no need to rollback } else { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } } throw; } catch (SagaException sex) { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } if (_log.IsErrorEnabled) { _log.Error("Saga Exception Occurred", sex); } } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } } }