I was wondering if it would be helpful (or a hindrance) if some of the pieces of information such as retry counts and the actual policy being used were configurable thereby preventing the need for a code change but instead a configuration change.
We could have a NuGet package, say Polly.Config, with one dependency (Polly). When installed, it automatically adds a configuration section to the project’s web config as a sort of template to be tweaked:
<configuration>
<configSections>
<section name="RetryPolicies" type="Polly.Config.RetryPoliciesSection.RetryPoliciesSection, Polly.Config" />
</configSections>
<RetryPolicies>
<Policies>
<Policy name="MyRetryPolicy" retryType="WaitAndRetry" retryDelayMs="25,50,75,100" />
<Policy name="None" retryType="None" />
<Policy name="RetryOnce" retryType="RetryOnce" />
<Policy name="RetryNTimes" retryType="RetryNTimes" retryCount="5" />
<Policy name="WaitAndRetry" retryType="WaitAndRetry" retryDelaysMs="50,100,150,200,250" />
<Policy name="RetryForever" retryType="RetryForever" />
<Policy name="CircuitBreaker" retryType="CircuitBreaker" triggerCount="5" timeoutMs="5000" />
</Policies>
</RetryPolicies>
</configuration>
These are just place-holder elements to showcase the available policies and how they should be configured. I added one at the top (i.e. MyRetryPolicy) that is referenced in the code snippet below.
Consuming a named policy in code looks like this (not using a name will default to a policy named “Default”):
public class RetryProvider : IRetryProvider
{
public Task<T> ExecuteAsync<T>(Func<Task<T>> executionFunction)
{
return ConfiguredPolicy.HandleAsync<Exception>("MyRetryPolicy")
.ExecuteAsync(executionFunction);
}
}
If we were to have done this using just Polly, it would have looked something like this:
public Task<T> ExecuteAsync<T>(Func<Task<T>> executionFunction)
{
return Policy
.Handle<Exception>()
.WaitAndRetryAsync(new [] { 25, 50, 75, 100}.Select(x => TimeSpan.FromMilliseconds(x)))
.ExecuteAsync(executionFunction);
}
You can see that all we’ve done is replace the configuration step. Now we can just inject this RetryProvider and use it anywhere we’re going over a network. For example,
RedisValue result = await retryProvider.ExecuteAsync(() => redisCache.StringGetAsync(key));
if (!result.HasValue)
return null;
Again the idea is that it would mean changing the magic numbers of the policy and even the policy itself becomes a configurable piece should that be desired and therefore can be quickly tweaked before and after something like performance testing or in production if problems arise.
Thoughts as to whether this is a good or bad idea?