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);
            }
        }
        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);
            }
        }
Beispiel #5
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);
        }
        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);
            }
        }
        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);
            }
        }
        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));
        }