public KeyValueDocument(string collectionId, string key, string data) { this.Id = DocumentIdHelper.GenerateId(collectionId, key); this.CollectionId = collectionId; this.Key = key; this.Data = data; }
public async Task HandleAsync(CreateDeliveryRequestCommand command, CancellationToken cancellationToken) { await requestValidator.ValidateAndThrowAsync(command, cancellationToken : cancellationToken).ConfigureAwait(false); using (var session = documentStore.OpenAsyncSession()) { var deliveryDocument = new Delivery { Id = DocumentIdHelper.GetDocumentId <Delivery>(documentStore, command.TransactionId), Status = DeliveryStatus.Created, Address = new Address { Country = command.Address.Country, City = command.Address.City, House = command.Address.House, State = command.Address.State, Street = command.Address.Street, Zip = command.Address.Zip } }; await session.StoreAsync(deliveryDocument, cancellationToken).ConfigureAwait(false); await session.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } }
private void EnsureValidId(string collectionId, string key = "") { // Currently, there is no official document describing valid character set of document ID // We just verified and enabled characters below var validCharacters = "_-"; if (!collectionId.All(c => char.IsLetterOrDigit(c) || validCharacters.Contains(c))) { var message = $"Invalid collectionId: '{collectionId}'"; logger.Info(message, () => new { collectionId }); throw new BadRequestException(message); } if (key.Any() && !key.All(c => char.IsLetterOrDigit(c) || validCharacters.Contains(c))) { var message = $"Invalid key: '{key}'"; logger.Info(message, () => new { key }); throw new BadRequestException(message); } // "The id is a user defined string, of up to 256 characters that is unique within the context of a specific parent resource." // - from https://docs.microsoft.com/en-us/azure/cosmos-db/documentdb-resources // But, currently portal.azure.com reject any document id contains 255 or more characters. // We just follow the experience result here: No more than 255 characters string id = DocumentIdHelper.GenerateId(collectionId, key); if (id.Length > 255) { var message = $"The collectionId/Key are too long: '{collectionId}', '{key}'"; logger.Info(message, () => new { collectionId, key, id }); throw new BadRequestException(message); } }
public async Task <ValueServiceModel> GetAsync(string collectionId, string key) { await this.SetupStorageAsync(); try { var docId = DocumentIdHelper.GenerateId(collectionId, key); var response = await this.client.ReadDocumentAsync($"{this.collectionLink}/docs/{docId}"); return(new ValueServiceModel(response)); } catch (Exception ex) { if (!this.exceptionChecker.IsNotFoundException(ex)) { throw; } const string message = "The resource requested doesn't exist."; this.log.Info(message, () => new { collectionId, key }); throw new ResourceNotFoundException(message); } }
public KeyValueDocument(string collectionId, string key, string data) { Id = DocumentIdHelper.GenerateId(collectionId, key); CollectionId = collectionId; Key = key; Data = data; }
protected async Task PerformRollbackStepAsync( string transactionId, Expression <Func <TTransaction, StepDetails> > stepSelector, Func <CancellationToken, Task> rollbackInvoker, CancellationToken cancellationToken) { using (var session = DocumentStore.OpenAsyncSession()) { var transactionDocumentId = DocumentIdHelper.GetDocumentId <TTransaction>(DocumentStore, transactionId); var transactionDocument = await session.LoadAsync <TTransaction>(transactionDocumentId, cancellationToken).ConfigureAwait(false); var stepDetails = stepSelector.Compile()(transactionDocument); var stepStatus = stepDetails.StepStatus; var rollbackAttempts = stepDetails.RollbackAttempts; var transactionStatus = transactionDocument.TransactionStatus; if (stepStatus == StepStatus.RolledBack || stepStatus == StepStatus.NotStarted) { return; } try { await rollbackInvoker(cancellationToken).ConfigureAwait(false); stepStatus = StepStatus.RolledBack; } catch { ++rollbackAttempts; if (rollbackAttempts > MaxRollbackAttemptsPerStep) { stepStatus = StepStatus.RollbackFailed; transactionStatus = TransactionStatus.RollbackFailed; } } var rollbackAttemptsMemberExpression = Expression.MakeMemberAccess(stepSelector, RollbackAttemptsPropertyInfo); var stepStatusMemberExpression = Expression.MakeMemberAccess(stepSelector, StepStatusPropertyInfo); var rollbackAttemptsMemberAccessor = Expression.Lambda <Func <TTransaction, int> >(rollbackAttemptsMemberExpression, stepSelector.Parameters); var stepStatusMemberAccessor = Expression.Lambda <Func <TTransaction, StepStatus> >(stepStatusMemberExpression, stepSelector.Parameters); session.Advanced.Patch(transactionDocument, t => t.TransactionStatus, transactionStatus); session.Advanced.Patch(transactionDocument, rollbackAttemptsMemberAccessor, rollbackAttempts); session.Advanced.Patch(transactionDocument, stepStatusMemberAccessor, stepStatus); if (stepStatus != StepStatus.RolledBack) { session.Advanced.Patch(transactionDocument, t => t.UtcDoNotExecuteBefore, DateTime.UtcNow.AddSeconds(DefaultRetryDelaySeconds)); } await session.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } }
protected async Task <TTransaction> GetTransactionByIdAsync(string transactionId, CancellationToken cancellationToken) { using (var session = DocumentStore.OpenAsyncSession()) { var transactionDocumentId = DocumentIdHelper.GetDocumentId <TTransaction>(DocumentStore, transactionId); var transactionDocument = await session.LoadAsync <TTransaction>(transactionDocumentId, cancellationToken).ConfigureAwait(false); return(transactionDocument); } }
protected async Task CompleteTransactionAsync(string transactionId, CancellationToken cancellationToken) { using (var session = DocumentStore.OpenAsyncSession()) { var transactionDocumentId = DocumentIdHelper.GetDocumentId <TTransaction>(DocumentStore, transactionId); var transactionDocument = await session.LoadAsync <TTransaction>(transactionDocumentId, cancellationToken).ConfigureAwait(false); session.Advanced.Patch(transactionDocument, t => t.TransactionStatus, TransactionStatus.Completed); await session.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } }
private async Task DeleteDataFromStorage(string tenantId, string collectionId, string key) { string docId = DocumentIdHelper.GenerateId(collectionId, key); try { await this.client.DeleteDocumentAsync(this.DocumentDbDatabaseId, this.GetPcsCollectionId(tenantId), docId); } catch (Exception) { } }
public async Task <string> HandleAsync(RegisterOrderCommand command, CancellationToken cancellationToken) { var transactionId = guidProvider.GenerateGuidString(); using (var session = documentStore.OpenAsyncSession()) { var transactionDocumentId = DocumentIdHelper.GetDocumentId <OrderTransaction>(documentStore, transactionId); var transactionDocument = new OrderTransaction { Id = transactionDocumentId, TransactionStatus = TransactionStatus.NotStarted, LoyaltyPointsConsumptionStepDetails = new StepDetails { Attempts = 0, RollbackAttempts = 0, StepStatus = StepStatus.NotStarted }, DeliveryCreationStepDetails = new StepDetails { Attempts = 0, RollbackAttempts = 0, StepStatus = StepStatus.NotStarted }, InventoryReservationStepDetails = new StepDetails { Attempts = 0, RollbackAttempts = 0, StepStatus = StepStatus.NotStarted }, OrderTotalStepDetails = new OrderTotalStepDetails { Attempts = 0, RollbackAttempts = 0, StepStatus = StepStatus.NotStarted, Total = 0 }, OrderDetails = new OrderDetails { UserId = command.UserId, Address = AddressMapper.ToEntity(command.Address), Items = OrderMapper.ToEntities(command.Order) } }; await session.StoreAsync(transactionDocument, cancellationToken).ConfigureAwait(false); await session.SaveChangesAsync().ConfigureAwait(false); } return(transactionId); }
public async Task DeleteAsync(string collectionId, string key) { try { string documentId = DocumentIdHelper.GenerateId(collectionId, key); await this.client.DeleteDocumentAsync(this.DocumentDbDatabaseId, this.DocumentDbCollectionId, documentId); } catch (Exception ex) { if (!this.exceptionChecker.IsNotFoundException(ex)) { throw; } this.logger.LogDebug("Key {key} does not exist, nothing to do"); } }
public async Task HandleAsync(CreateOrderCommand command, CancellationToken cancellationToken) { var transactionId = DocumentIdHelper.GetEntityId <OrderTransaction>(DocumentStore, command.TransactionId); var(totalCost, getTotalCostStepResult) = await GetAndSaveOrderTotalAsync(transactionId, command, cancellationToken).ConfigureAwait(false); if (getTotalCostStepResult == StepResult.Abort) { // There should be nothing to rolled back, but this marks the transaction as failed so it won't be rescheduled. await RollbackAsync(transactionId, cancellationToken).ConfigureAwait(false); return; } else if (getTotalCostStepResult == StepResult.Retry) { return; } await ConsumeLoyaltyPointsAsync(transactionId, totalCost, command.UserId, cancellationToken).ConfigureAwait(false); await ReserveItemsAsync(transactionId, command, cancellationToken).ConfigureAwait(false); var transactionDocument = await GetTransactionByIdAsync(transactionId, cancellationToken).ConfigureAwait(false); if (transactionDocument.InventoryReservationStepDetails.StepStatus == StepStatus.Completed && transactionDocument.LoyaltyPointsConsumptionStepDetails.StepStatus == StepStatus.Completed) { await CreateDeliveryRequestAsync(transactionId, command, cancellationToken).ConfigureAwait(false); } transactionDocument = await GetTransactionByIdAsync(transactionId, cancellationToken).ConfigureAwait(false); if (transactionDocument.TransactionStatus == TransactionStatus.PermanentFailure) { await RollbackAsync(transactionId, cancellationToken).ConfigureAwait(false); return; } if (transactionDocument.DeliveryCreationStepDetails.StepStatus == StepStatus.Completed && transactionDocument.InventoryReservationStepDetails.StepStatus == StepStatus.Completed && transactionDocument.LoyaltyPointsConsumptionStepDetails.StepStatus == StepStatus.Completed) { await CompleteTransactionAsync(transactionId, cancellationToken).ConfigureAwait(false); } }
public async Task DeleteAsync(string collectionId, string key) { await this.SetupStorageAsync(); try { await this.client.DeleteDocumentAsync($"{this.collectionLink}/docs/{DocumentIdHelper.GenerateId(collectionId, key)}"); } catch (Exception ex) { if (!this.exceptionChecker.IsNotFoundException(ex)) { throw; } this.log.Debug("Key does not exist, nothing to do", () => new { key }); } }
public static Operations.DataStructures.Delivery ToServiceContract(IDocumentStore documentStore, Entities.Delivery deliveryEntity) { if (documentStore == null) { throw new ArgumentNullException(nameof(documentStore)); } if (deliveryEntity == null) { return(null); } var id = DocumentIdHelper.GetEntityId <Entities.Delivery>(documentStore, deliveryEntity.Id); return(new Operations.DataStructures.Delivery( id, AddressMapper.ToServiceContract(deliveryEntity.Address), DeliveryStatusMapper.ToServiceContract(deliveryEntity.Status))); }
private async Task RollbackAsync(string transactionId, CancellationToken cancellationToken) { await RollbackLoyaltyPointsConsumptionAsync(transactionId, cancellationToken).ConfigureAwait(false); await RollbackInventoryReservationAsync(transactionId, cancellationToken).ConfigureAwait(false); await RollbackDeliveryCreationAsync(transactionId, cancellationToken).ConfigureAwait(false); using (var session = DocumentStore.OpenAsyncSession()) { var transactionDocumentId = DocumentIdHelper.GetDocumentId <OrderTransaction>(DocumentStore, transactionId); var transactionDocument = await session.LoadAsync <OrderTransaction>(transactionDocumentId, cancellationToken).ConfigureAwait(false); if (transactionDocument.TransactionStatus == TransactionStatus.RolledBack) { return; } session.Advanced.Patch(transactionDocument, t => t.TransactionStatus, TransactionStatus.RolledBack); await session.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } }
public async Task <ValueServiceModel> GetAsync(string collectionId, string key) { try { var docId = DocumentIdHelper.GenerateId(collectionId, key); var response = await this.client.ReadDocumentAsync(this.DocumentDbDatabaseId, this.DocumentDbCollectionId, docId); return(new ValueServiceModel(response)); } catch (Exception ex) { if (!this.exceptionChecker.IsNotFoundException(ex)) { throw; } const string message = "The resource requested doesn't exist."; this.logger.LogInformation(message + " {collection ID {collectionId}, key {key}", collectionId, key); throw new ResourceNotFoundException(message); } }
private async Task <(int total, StepResult stepResult)> GetAndSaveOrderTotalAsync(string transactionId, CreateOrderCommand command, CancellationToken cancellationToken) { using (var session = DocumentStore.OpenAsyncSession()) { var transactionDocumentId = DocumentIdHelper.GetDocumentId <OrderTransaction>(DocumentStore, transactionId); var transactionDocument = await session.LoadAsync <OrderTransaction>(transactionDocumentId, cancellationToken).ConfigureAwait(false); if (transactionDocument.OrderTotalStepDetails.StepStatus == StepStatus.Completed) { return(transactionDocument.OrderTotalStepDetails.Total, StepResult.Successful); } var totalCost = 0; var attempts = transactionDocument.OrderTotalStepDetails.Attempts; var transactionStatus = transactionDocument.TransactionStatus; var stepStatus = transactionDocument.OrderTotalStepDetails.StepStatus; var stepResult = StepResult.Successful; foreach (var orderItem in command.Order.Items) { try { var product = await catalogApiClient.GetItemAsync(orderItem.ProductId, cancellationToken).ConfigureAwait(false); totalCost += product.Result.PointsCost; } catch (SwaggerException e) when(e.StatusCode == NotFoundStatusCode) { stepResult = StepResult.Abort; break; } catch { stepResult = StepResult.Retry; break; } } if (stepResult == StepResult.Successful) { stepStatus = StepStatus.Completed; } else if (stepResult == StepResult.Retry) { attempts++; stepStatus = StepStatus.TemporalFailure; if (attempts > MaxAttemptsPerStep) { stepStatus = StepStatus.PermanentFailure; stepResult = StepResult.Abort; } } else { stepStatus = StepStatus.PermanentFailure; } if (stepStatus == StepStatus.PermanentFailure) { transactionStatus = TransactionStatus.PermanentFailure; } session.Advanced.Patch(transactionDocument, t => t.TransactionStatus, transactionStatus); session.Advanced.Patch(transactionDocument, t => t.OrderTotalStepDetails.Attempts, attempts); session.Advanced.Patch(transactionDocument, t => t.OrderTotalStepDetails.StepStatus, stepStatus); if (stepStatus == StepStatus.Completed) { session.Advanced.Patch(transactionDocument, t => t.OrderTotalStepDetails.Total, totalCost); } await session.SaveChangesAsync(cancellationToken).ConfigureAwait(false); return(totalCost, stepResult); } }
public static Task <Delivery> LoadDeliveryAsync(this IAsyncDocumentSession session, string id, CancellationToken cancellationToken) { var internalId = DocumentIdHelper.GetDocumentId <Delivery>(session.Advanced.DocumentStore, id); return(session.LoadAsync <Delivery>(internalId, cancellationToken)); }
protected async Task PerformTransactionStepAsync( string transactionId, Expression <Func <TTransaction, StepDetails> > stepSelector, Func <CancellationToken, Task <StepResult> > stepInvoker, CancellationToken cancellationToken) { using (var session = DocumentStore.OpenAsyncSession()) { var transactionDocumentId = DocumentIdHelper.GetDocumentId <TTransaction>(DocumentStore, transactionId); var transactionDocument = await session.LoadAsync <TTransaction>(transactionDocumentId, cancellationToken).ConfigureAwait(false); var stepDetails = stepSelector.Compile()(transactionDocument); if (stepDetails.StepStatus == StepStatus.Completed || stepDetails.StepStatus == StepStatus.PermanentFailure || stepDetails.StepStatus == StepStatus.RollbackFailed || stepDetails.StepStatus == StepStatus.RolledBack) { return; } if (transactionDocument.TransactionStatus != TransactionStatus.NotStarted || transactionDocument.TransactionStatus != TransactionStatus.InProgress) { return; } StepResult stepResult; var stepStatus = StepStatus.InProgress; var attempts = stepDetails.Attempts; try { stepResult = await stepInvoker(cancellationToken).ConfigureAwait(false); } catch { stepResult = StepResult.Retry; } if (stepResult == StepResult.Retry) { ++attempts; stepStatus = StepStatus.TemporalFailure; if (stepDetails.Attempts > MaxAttemptsPerStep) { stepStatus = StepStatus.PermanentFailure; } } else if (stepResult == StepResult.Abort) { stepStatus = StepStatus.PermanentFailure; } else { stepStatus = StepStatus.Completed; } var attemptsMemberExpression = Expression.MakeMemberAccess(stepSelector, RollbackAttemptsPropertyInfo); var stepStatusMemberExpression = Expression.MakeMemberAccess(stepSelector, StepStatusPropertyInfo); var attemptsMemberAccessor = Expression.Lambda <Func <TTransaction, int> >(attemptsMemberExpression, stepSelector.Parameters); var stepStatusMemberAccessor = Expression.Lambda <Func <TTransaction, StepStatus> >(stepStatusMemberExpression, stepSelector.Parameters); session.Advanced.Patch(transactionDocument, attemptsMemberAccessor, attempts); session.Advanced.Patch(transactionDocument, stepStatusMemberAccessor, stepStatus); if (stepStatus == StepStatus.PermanentFailure) { session.Advanced.Patch(transactionDocument, t => t.TransactionStatus, TransactionStatus.PermanentFailure); } if (stepStatus == StepStatus.TemporalFailure) { session.Advanced.Patch(transactionDocument, t => t.UtcDoNotExecuteBefore, DateTime.UtcNow.AddSeconds(DefaultRetryDelaySeconds)); } await session.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } }