I'm trying to implement a custom resilience policy for an API, based on these Polly .NET Policies: Retry + Timeout + Circuit Breaker.
A. To keep in mind:
I don't want to use Policy Wraps. This is not necessary at this point. I just want to use a nested policy. Later I will consider wrapping.
I need to do something simple, just for testing / trying the real benefits of this strategy.
I don't want to use Fallback. I want to keep the server response intact.
B. What I pretend?
- Have a Retry Policy that retries three times in a row with a N seconds between retries.
- Have a Timeout Policy that fails quickly if HttpClient spends more than M seconds to respond.
- Have a Circuit Breaker that broken the circuit to avoid "send" requests if N+1 retries have failed in a row.
C. What I've coded?
Declaring some variables:
const int NUMBER_OF_RETRIES_PER_REQUEST = 3; //TIME_IN_SECONDS
const int MAX_TIME_FOR_HTTP_REQUEST = 10; //TIME_IN_SECONDS
const int TIME_TO_WAIT_BETWEEN_RETRIES = 100; //TIME_IN_SECONDS
static HttpClient httpClient = new HttpClient()
{
Timeout = new TimeSpan(0, 0, MAXSecondsHTTPTimeout)
};
Defining Timeout Policy:
var timeoutPollyPolicy = Policy.TimeoutAsync(MAX_TIME_FOR_HTTP_REQUEST / 2);
Defining Retry Policy:
var retryPollyPolicy = Policy.HandleResult<HttpResponseMessage>(
response => { return STATUS_TO_HANDLE.Contains(response.StatusCode); })
.WaitAndRetryAsync(
NUMBER_OF_RETRIES_PER_REQUEST,
(retryAttempt) => TimeSpan.FromSeconds(TIME_TO_WAIT_BETWEEN_RETRIES),
(DelegateResult<HttpResponseMessage> lastResponse,
TimeSpan waitTime, int retryCount, Context context) =>
{
//ACTIONS TO BE EXECUTED IN EACH RETRY
}
);
Executing policies
public async Task<HttpResponseMessage>
Send(HttpRequestMessage req, CancellationToken tokenToStop)
{
try
{
//OBS. Timeout and Retry Policies are defined here just for this example
//I'm defining these policies globally for all HttpClients
var httpResponse = await retryPollyPolicy.ExecuteAsync(() =>
timeoutPollyPolicy.ExecuteAsync(async (CancellationToken cancellationToken) =>
{
//cancellationToken is innherent to TimeoutAsync
//cancelling the inner request for timeoutPollyPolicy
return = await httpClient.SendAsync(req, cancellationToken);
//tokenToStop is received to stop / cancel the external request for retryPolicy
},tokenToStop));
return httpResponse;
}
catch (Exception ex)
{
//HANDLE_EXCEPTION, LOG. Not throw necessarily.
throw ex;
}
}
D. What I'm trying to define / to understand?
- What must be the innermost policy in this method?
What is the difference in the CircuitBreaker behavior if I write the CircuitBReaker inside/outside the TimeoutPolicy?
Do I need to add a CancellationToken to the CircuitBreaker Policy?
Aside of readability, what other resilience benefits do I have if I use Wraps?