Transient fault handling in Sitecore Commerce

Transient fault handling can be cumbersome and lead developers to write repetitive code. There could be many situations when you want your code to be retried in case of failures/exceptions etc. While working with a few external system integrations, we had to implement a retry mechanism for certain transactional requests. Payment integrations are one of the examples that I am using here.

Polly is a library that allows developers to express resilience and transient fault handling policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Almost all the payment providers we integrated to Sitecore Commerce project e.g. Braintree/DiectBank/Openpay recommend to retry the transactional requests in case of timeouts or internet issues.

For example when we request Braintree payment gateway to settle the payment if the request times out you have to retry the same request 3 times before you treat it as a failure.

Retry

Since the retry could be handled as cross-cutting, we decided to write some code to do automatic retries for. However, with a little research, we came across this API called Polly. Polly can help you manage transient faults like a charm.

We installed Polly nuget package in our project and started using it. Polly has a Policy class which can be used to configure the retry policy e.g. Retry should only occur for HttpRequestException etc. Since Policy class has static functions to work, we decided to create a wrapper around it to make it injectable. We called this wrapper RetryPayment and it implements IRetryPayment.

 

public interface IRetryPayment
    {
        /// <summary>
        /// Executes the asynchronous.
        /// </summary>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="func">The function.</param>
        /// <param name="transactionRetry">The transaction retry.</param>
        /// <returns></returns>
        Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> func, int transactionRetry);
    }

While the implementation looks like

    public class RetryPayment : IRetryPayment
    {
        /// <summary>
        /// Executes the asynchronous.
        /// </summary>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="func">The function.</param>
        /// <param name="transactionRetry">The transaction retry.</param>
        /// <returns></returns>
        public async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> func, int transactionRetry)
        {
            var pollyPolicy = Policy
                .Handle<HttpRequestException>()
                .Or<WebException>()
                .Or<TaskCanceledException>()
                .WaitAndRetryAsync(transactionRetry, retryAttempt =>
                    TimeSpan.FromSeconds(5 * retryAttempt)
                );

            return await pollyPolicy.ExecuteAsync(func);
        }
    }

And yes, that easy it is. 5 * retryAttempt is interesting here. First retry will be made with a delay of 5secs wait while 2nd with 10secs and so on.

IRetryPayment is injectable

            services.AddTransient<IRetryPayment, RetryPayment>(); //In ConfigureSitecore

Then we injected this in SettlePaymentBlock

        public SettlePaymentBlock(IBraintreeGateway braintreeGateway, 
            IPolicyValidator<BraintreeClientPolicy> policyValidator,
            IRetryPayment retryPayment)
        {
            this._braintreeGateway = braintreeGateway;
            this._policyValidator = policyValidator;
            this._retryPayment = retryPayment;
        }
        //SettlePaymentBlockConstructore

And while executing the request

var result = await _retryPayment.ExecuteAsync(
            () => _braintreeGateway.Transaction
                                   .SaleAsync(request), 2); //2 IS TOTAL NUMBER OF RETRIES

Happy coding!!!!!

Leave a Reply