/// <inheritdoc/> public Task SaveSaga(object saga, QueueContext context) { var sagaType = saga.GetType(); var nameProperty = sagaType.GetProperty("SagaName"); var sagaName = (string)nameProperty.GetValue(saga); // if the saga is complete, we can delete the data. var completeProperty = sagaType.GetProperty("SagaComplete"); bool IsComplete = completeProperty.GetValue(saga) is bool completeValue && completeValue; if (IsComplete) { return(_dataAccess.DeleteSagaData(sagaName, context.SagaKey)); } // the saga is not complete, serialize it. var dataProperty = sagaType.GetProperty("Data"); var dataObject = dataProperty.GetValue(saga); if (dataObject == null) { dataObject = Activator.CreateInstance(dataProperty.PropertyType); } var serializedData = _serializer.SerializeSaga(dataObject, dataProperty.PropertyType); if (context.SagaData == null) { // we have never persisted saga data for this instance (Message was a saga start message). // create a new row in the DB. context.SagaData = new SagaData { SagaId = Guid.NewGuid(), Key = context.SagaKey, Data = serializedData }; // if two start messages are processed at the same time, two inserts could occur on different threads. // if that happens, the second insert is expected to throw a duplicate key constraint violation. return(_dataAccess.Insert(context.SagaData, sagaName)); } else { // update the existing row. context.SagaData.Data = serializedData; return(_dataAccess.Update(context.SagaData, sagaName)); } }
/// <inheritdoc/> public Task Fail(QueueContext context, Exception exception) { context.MessageData.Retries++; context.MessageData.NotBefore = _clock.UtcNow.AddSeconds(5 * context.MessageData.Retries); // Wait longer between retries. context.Headers.ExceptionDetails = exception.ToString(); context.MessageData.Headers = _serializer.SerializeHeaders(context.Headers); if (context.MessageData.Retries >= MaxRetries) { _log.Error($"Message {context.MessageData.MessageId} exceeded max retries ({MaxRetries}) and has failed."); context.MessageData.Failed = DateTime.UtcNow; _counters.FailMessage(); return(_dataAccess.FailMessage(context.MessageData, context.SourceQueue)); } else { _log.Error($"Message {context.MessageData.MessageId} will be retried at {context.MessageData.NotBefore}."); _counters.RetryMessage(); return(_dataAccess.Update(context.MessageData, context.SourceQueue)); } }
/// <inheritdoc/> public async Task LoadSaga(object saga, QueueContext context) { // work out the class name of the saga. var sagaType = saga.GetType(); var nameProperty = sagaType.GetProperty("SagaName"); var sagaName = (string)nameProperty.GetValue(saga); // fetch the data from the DB. context.SagaData = await _dataAccess.GetSagaData(sagaName, context.SagaKey); if (context.SagaData != null && context.SagaData.Blocked) { return; } // Hypothetically locked could be false, and sagadata could be null if the saga hasn't been started. // and if two saga starts are processed at the same time, a second insert will occur and // that will fail with a duplicate key constraint. // determine the type to deserialze to or create. var dataProperty = sagaType.GetProperty("Data"); var sagaDataType = dataProperty.PropertyType; object dataObject; if (context.SagaData == null) { // no data in the DB, create a new object. dataObject = Activator.CreateInstance(sagaDataType); } else { // deserialize // try catch needed? Probably better to throw and let the error handling deal with it. // Someone may have to fix the saga data and retry the failed message though. dataObject = _serializer.DeserializeSaga(context.SagaData.Data, sagaDataType); } // assign the data to the saga. dataProperty.SetValue(saga, dataObject); }
/// <inheritdoc/> public Task DelayMessage(QueueContext messageContext, int milliseconds) { messageContext.MessageData.NotBefore = _clock.UtcNow.AddMilliseconds(milliseconds); _counters.DelayMessage(); return(_dataAccess.Update(messageContext.MessageData, messageContext.SourceQueue)); }
/// <inheritdoc/> public Task Complete(QueueContext messageContext) { messageContext.MessageData.Completed = _clock.UtcNow; _counters.CompleteMessage(); return(_dataAccess.CompleteMessage(messageContext.MessageData, messageContext.SourceQueue)); }