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);
            }
        }
Exemplo n.º 3
0
        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);
            }
        }
Exemplo n.º 4
0
        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);
            }
        }
Exemplo n.º 5
0
 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)
            {
            }
        }
Exemplo n.º 10
0
        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);
            }
        }
Exemplo n.º 13
0
        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 });
            }
        }
Exemplo n.º 14
0
        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);
            }
        }