There is an extension method that registers IAccountSearchServiceClient
with some policy handlers looks like Polly lib is used
public static IServiceCollection AddAccountSearchServiceClient(this IServiceCollection services)
{
services.AddHttpClient<AccountSearchServiceClient>()
.ConfigureHttpClient((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<AccountSearchSettings>>();
var settings = options.Value;
client.BaseAddress = settings.BaseUrl;
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (m, c, cc, pe) => true
};
return handler;
})
.AddPolicyHandler(request => request.Method == HttpMethod.Get ? Policies.ShortTimeout : Policies.LongTimeout)
.AddPolicyHandlerFromRegistry("circuitBreaker")
.AddPolicyHandlerFromRegistry("bulkhead")
.AddPolicyHandlerFromRegistry("RetryPolicy");
services.AddScoped<IAccountSearchServiceClient>(sp => sp.GetRequiredService<AccountSearchServiceClient>());
return services;
}
at runtime getting such a DI error:
System.InvalidOperationException HResult=0x80131509 Message=No service for type 'Polly.Registry.IReadOnlyPolicyRegistry`1[System.String]' has been registered.
Source=Microsoft.Extensions.DependencyInjection.Abstractions
the error occurs here
sp.GetRequiredService<AccountSearchServiceClient>()
I'm not very familiar with Polly. Is there something missing?
I've put a break point on a constructor but the ctor is not called error happens earlier after ConfigurePrimaryHttpMessageHandler
the consturctor looks as the following:
public AccountSearchServiceClient(HttpClient httpClient, IOptions<AccountSearchSettings> settings)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings));
}
no direct injections or usages of IReadOnlyPolicyRegistry
I guess it's something internal type of Polly
It turned out the missing part was the policy registration:
var policyRegistry = services.AddPolicyRegistry();
policyRegistry["circuitBreaker"] = HttpPolicyExtensions.HandleTransientHttpError()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(20));
policyRegistry["RetryPolicy"] = ...;
//etc.
Even though it seems quite appealing to have a registry with a full of policies unfortunately it is error-prone. At the first glace it seems that it provides great flexibility by allowing to the consumer to be able to combine whatever policies he/she wants.
But there is a problem, which is related to the escalation. If the inner policy fails then Polly will escalate the problem to the next outer policy (in the policy chain) which may or may not aware of the inner policy.
Let's stick with your example where you have a Retry and a Circuit Breaker policies.
The consumer can register them in the following two orders:
BrokenCircuitException
.
Let's suppose you have a Timeout policy as well. This is where things can get quite complicated.
The Timeout can be used either as a local or a global timeout:
+1. Timeout >> Retry >> Circuit Breaker >> Timeout
TimeoutRejectedException
.BrokenCircuitException
...Fortunately Polly can help us with Wrap. With this you can explicitly combine two or more policies in a given order. This helps you define strategies not just individual policies.
So instead of exposing policies you can expose strategies which combines a set of policies to achieve the desired behaviour.
Let's stick with the Retry >> Circuit Breaker >> Timeout example:
HttpRequestExcetion
, TimeotRejectedException
HttpRequestExcetion
, TimeotRejectedException
, BrokenCircuitException
Let's suppose we have the following abstraction to specify the parameters of the above three policies:
public class ResilienceSettings
{
public string BaseAddress { get; set; }
public int HttpRequestTimeoutInMilliseconds { get; set; }
public int HttpRequestRetrySleepDurationInMilliseconds { get; set; }
public int HttpRequestRetryCount { get; set; }
public int HttpRequestCircuitBreakerFailCountInCloseState { get; set; }
public int HttpRequestCircuitBreakerDelayInMillisecondsBetweenOpenAndHalfOpenStates { get; set; }
}
Now, let's see the policies:
private static IAsyncPolicy<HttpResponseMessage> TimeoutPolicy(ResilienceSettings settings)
=> Policy
.TimeoutAsync<HttpResponseMessage>( //Catches TaskCanceledException and throws instead TimeoutRejectedException
timeout: TimeSpan.FromMilliseconds(settings.HttpRequestTimeoutInMilliseconds));
private static IAsyncPolicy<HttpResponseMessage> CircuitBreakerPolicy(ResilienceSettings settings)
=> HttpPolicyExtensions
.HandleTransientHttpError() //Catches HttpRequestException or checks the status code: 5xx or 408
.Or<TimeoutRejectedException>() //Catches TimeoutRejectedException, which can be thrown by an inner TimeoutPolicy
.CircuitBreakerAsync( //Monitors consecutive failures
handledEventsAllowedBeforeBreaking: settings
.HttpRequestCircuitBreakerFailCountInCloseState, //After this amount of consecutive failures it will break
durationOfBreak: TimeSpan.FromMilliseconds(settings
.HttpRequestCircuitBreakerDelayInMillisecondsBetweenOpenAndHalfOpenStates)); //After this amount of delay it will give it a try
private static IAsyncPolicy<HttpResponseMessage> RetryPolicy(ResilienceSettings settings)
=> HttpPolicyExtensions
.HandleTransientHttpError() //Catches HttpRequestException or checks the status code: 5xx or 408
.Or<BrokenCircuitException>() //Catches BrokenCircuitException, so whenever the broker is open then it refuses new requests
.Or<TimeoutRejectedException>() //Catches TimeoutRejectedException, which can be thrown by an inner TimeoutPolicy
.WaitAndRetryAsync( //Monitors the above anomalies
retryCount: settings.HttpRequestRetryCount, //After (this amount + 1) attempts it gives up
sleepDurationProvider: _ =>
TimeSpan.FromMilliseconds(settings.HttpRequestRetrySleepDurationInMilliseconds)); //After a failed attempt it delays the next try with this amount of time
And finally the extension method for registration:
public static class ResilientHttpClientRegister
{
public static IServiceCollection AddXYZResilientStrategyToHttpClientProxy<TInterface, TImplementation>
(this IServiceCollection services, ResilienceSettings settings)
where TInterface: class
where TImplementation: class, TInterface
{
var (serviceUri, combinedPolicy) = CreateParametersForXYZStrategy<TInterface>(settings);
services.AddHttpClient<TInterface, TImplementation>(
client => { client.BaseAddress = serviceUri; })
.AddPolicyHandler(combinedPolicy); //Retry > Circuit Breaker > Timeout (outer > inner)
return services;
}
private static (Uri, IAsyncPolicy<HttpResponseMessage>) CreateParametersForXYZStrategy<TInterface>(ResilienceSettings settings)
{
Uri serviceUri = Uri.TryCreate(settings.BaseAddress, UriKind.Absolute, out serviceUri)
? serviceUri
: throw new UriFormatException(
$"Invalid url was set for the '{typeof(TInterface).Name}' resilient http client. " +
$"Its value was '{HttpUtility.UrlEncode(settings.BaseAddress)}'");
var combinedPolicy = Policy.WrapAsync(RetryPolicy(settings), CircuitBreakerPolicy(settings), TimeoutPolicy(settings));
return (serviceUri, combinedPolicy);
}
}
User contributions licensed under CC BY-SA 3.0