Changes using fluent configuration API from Polly. Still to be cleaned up and old Policy code to be removed, etc.
This commit is contained in:
parent
18e6dc4afb
commit
dc5de83747
@ -1,5 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Polly;
|
||||
using Polly.CircuitBreaker;
|
||||
using Polly.Retry;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
@ -7,8 +9,13 @@ namespace WebMVC.Infrastructure
|
||||
{
|
||||
public class HttpClientDefaultPolicies
|
||||
{
|
||||
const int RETRY_COUNT = 6;
|
||||
const int EXCEPTIONS_ALLOWED_BEFORE_CIRCUIT_BREAKER = 5;
|
||||
//Config for Retries with exponential backoff policy
|
||||
const int MAX_RETRIES = 6;
|
||||
const int SECONDS_BASE_FOR_EXPONENTIAL_BACKOFF = 2;
|
||||
|
||||
//Config for Circuit Breaker policy
|
||||
const int EXCEPTIONS_ALLOWED_BEFORE_CIRCUIT_BREAKES = 5;
|
||||
const int DURATION_OF_BREAK_IN_MINUTES = 1;
|
||||
|
||||
private readonly ILogger<HttpClientDefaultPolicies> _logger;
|
||||
|
||||
@ -17,44 +24,51 @@ namespace WebMVC.Infrastructure
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public Policy GetWaitAndRetryPolicy()
|
||||
public IAsyncPolicy<HttpResponseMessage> GetWaitAndRetryPolicy()
|
||||
{
|
||||
return Policy.Handle<HttpRequestException>()
|
||||
.WaitAndRetryAsync(
|
||||
// number of retries
|
||||
RETRY_COUNT,
|
||||
// exponential backofff
|
||||
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
||||
// on retry
|
||||
(exception, timeSpan, retryCount, context) =>
|
||||
{
|
||||
var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
|
||||
$"of {context.PolicyKey} " +
|
||||
$"at {context.OperationKey}, " +
|
||||
$"due to: {exception}.";
|
||||
_logger.LogWarning(msg);
|
||||
_logger.LogDebug(msg);
|
||||
});
|
||||
RetryPolicy retryPolicy =
|
||||
Policy.Handle<HttpRequestException>()
|
||||
.WaitAndRetryAsync(
|
||||
// Maximum number of retries
|
||||
MAX_RETRIES,
|
||||
// exponential backofff (2sg, 4sg, 8sg, 16sg, 32sg. etc.)
|
||||
retryAttempt => TimeSpan.FromSeconds(Math.Pow(SECONDS_BASE_FOR_EXPONENTIAL_BACKOFF, retryAttempt)),
|
||||
// on retry
|
||||
(exception, timeSpan, retryCount, context) =>
|
||||
{
|
||||
var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
|
||||
$"of {context.PolicyKey} " +
|
||||
$"at {context.OperationKey}, " +
|
||||
$"due to: {exception}.";
|
||||
_logger.LogWarning(msg);
|
||||
_logger.LogDebug(msg);
|
||||
});
|
||||
|
||||
return retryPolicy.AsAsyncPolicy<HttpResponseMessage>();
|
||||
}
|
||||
|
||||
public Policy GetCircuitBreakerPolicy()
|
||||
public IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
|
||||
{
|
||||
return Policy.Handle<HttpRequestException>()
|
||||
.CircuitBreakerAsync(
|
||||
// number of exceptions before breaking circuit
|
||||
EXCEPTIONS_ALLOWED_BEFORE_CIRCUIT_BREAKER,
|
||||
// time circuit opened before retry
|
||||
TimeSpan.FromMinutes(1),
|
||||
(exception, duration) =>
|
||||
{
|
||||
// on circuit opened
|
||||
_logger.LogTrace("Circuit breaker opened");
|
||||
},
|
||||
() =>
|
||||
{
|
||||
// on circuit closed
|
||||
_logger.LogTrace("Circuit breaker reset");
|
||||
});
|
||||
CircuitBreakerPolicy circuitBreakerPolicy =
|
||||
Policy.Handle<HttpRequestException>()
|
||||
.CircuitBreakerAsync(
|
||||
// Number of exceptions before breaking the circuit
|
||||
EXCEPTIONS_ALLOWED_BEFORE_CIRCUIT_BREAKES,
|
||||
// Duration of break
|
||||
TimeSpan.FromMinutes(DURATION_OF_BREAK_IN_MINUTES),
|
||||
(exception, duration) =>
|
||||
{
|
||||
// On circuit opened (Circuit is broken)
|
||||
_logger.LogTrace("Circuit has been broken");
|
||||
},
|
||||
() =>
|
||||
{
|
||||
// on circuit closed
|
||||
_logger.LogTrace("Circuit has been reset");
|
||||
});
|
||||
|
||||
|
||||
return circuitBreakerPolicy.AsAsyncPolicy<HttpResponseMessage>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,11 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Polly;
|
||||
using Polly.CircuitBreaker;
|
||||
using Polly.Extensions.Http;
|
||||
using Polly.Registry;
|
||||
using Polly.Retry;
|
||||
using Polly.Timeout;
|
||||
using StackExchange.Redis;
|
||||
using System;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
@ -35,13 +39,13 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// This method gets called by the runtime. Use this method to add services to the IoC container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddAppInsight(Configuration)
|
||||
.AddHealthChecks(Configuration)
|
||||
.AddCustomMvc(Configuration)
|
||||
.AddCustomApplicationServices(Configuration)
|
||||
.AddHttpClientServices(Configuration)
|
||||
.AddCustomAuthentication(Configuration);
|
||||
}
|
||||
|
||||
@ -165,28 +169,89 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCustomApplicationServices(this IServiceCollection services, IConfiguration configuration)
|
||||
// Adds all Http client services (like Service-Agents) using resilient Http requests based on HttpClient factory and Polly's policies
|
||||
public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
// (CDLTLL) TEMPORAL COMMENT: Do we need this line of code if using HttpClient factory?
|
||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
|
||||
// (CDLTLL) I don't think we need this HttpClientDefaultPolicies if using fluent configuration ,as below...
|
||||
// Create a singleton object with the by default policies
|
||||
services.AddSingleton<HttpClientDefaultPolicies>();
|
||||
var defaultPolicies = services.BuildServiceProvider().GetService<HttpClientDefaultPolicies>();
|
||||
|
||||
var registry = services.AddPolicyRegistry();
|
||||
registry.Add("WaitAndRetry", defaultPolicies.GetWaitAndRetryPolicy());
|
||||
registry.Add("CircuitBreaker", defaultPolicies.GetCircuitBreakerPolicy());
|
||||
// Create a Polly-Policy-Registry with the by default policies for resilient Http requests
|
||||
var pollyPolicyRegistry = services.AddPolicyRegistry();
|
||||
|
||||
|
||||
//Using fluent client configuration of Polly policies
|
||||
|
||||
var retriesWithExponentialBackoff = HttpPolicyExtensions
|
||||
.HandleTransientHttpError()
|
||||
.WaitAndRetryAsync(7,
|
||||
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
|
||||
);
|
||||
//(CDLTLL) Instead of hardcoded values, use: configuration["HttpClientMaxNumberRetries"], configuration["SecondsBaseForExponentialBackoff"]
|
||||
|
||||
var circuitBreaker = HttpPolicyExtensions
|
||||
.HandleTransientHttpError()
|
||||
.CircuitBreakerAsync(6,
|
||||
TimeSpan.FromSeconds(30)
|
||||
);
|
||||
//(CDLTLL) Instead of hardcoded values, use: configuration["HttpClientExceptionsAllowedBeforeBreaking"], configuration["DurationOfBreakInMinutes"]
|
||||
|
||||
|
||||
pollyPolicyRegistry.Add("DefaultRetriesWithExponentialBackoff", retriesWithExponentialBackoff);
|
||||
pollyPolicyRegistry.Add("DefaultCircuitBreaker", circuitBreaker);
|
||||
|
||||
|
||||
// (CDLTLL) Using "OLD" policies. I don't like it much...
|
||||
//pollyPolicyRegistry.Add("DefaultRetriesWithExponentialBackoff", defaultPolicies.GetWaitAndRetryPolicy()); // (CDLTLL) TEMPORAL COMMENT: Arguments here would be a good place to propagate the configuration values --> GetWaitAndRetryPolicy(configuration["HttpClientMaxNumberRetries"], configuration["SecondsBaseForExponentialBackoff"])
|
||||
//pollyPolicyRegistry.Add("DefaultCircuitBreaker", defaultPolicies.GetCircuitBreakerPolicy()); // (CDLTLL) TEMPORAL COMMENT: Arguments here would be a good place to propagate the configuration values --> GetCircuitBreakerPolicy(configuration["HttpClientExceptionsAllowedBeforeBreaking"], configuration["DurationOfBreakInMinutes"])
|
||||
|
||||
|
||||
|
||||
// (CDLTLL) Handlers need to be transient. //(CDLTLL) TEMPORAL COMMENT: This registration was missing, hence the error "InvalidOperationException: No service for type 'WebMVC.Infrastructure.HttpClientAuthorizationDelegatingHandler' has been registered"
|
||||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
|
||||
|
||||
//Add all Typed-Clients (Service-Agents) through the HttpClient factory to implement Resilient Http requests
|
||||
//
|
||||
|
||||
//Add BasketService typed client (Service Agent)
|
||||
services.AddHttpClient<IBasketService, BasketService>()
|
||||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
|
||||
.AddPolicyHandlerFromRegistry("WaitAndRetry")
|
||||
.AddPolicyHandlerFromRegistry("CircuitBreaker");
|
||||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>() //Additional Authentication-Delegating-Handler to add the OAuth-Bearer-token to the Http headers
|
||||
.AddPolicyHandlerFromRegistry("DefaultRetriesWithExponentialBackoff")
|
||||
.AddPolicyHandlerFromRegistry("DefaultCircuitBreaker");
|
||||
|
||||
//Add CatalogService typed client (Service Agent)
|
||||
//services.AddHttpClient<ICatalogService, CatalogService>() ...
|
||||
|
||||
//Add OrderingService typed client (Service Agent)
|
||||
//services.AddHttpClient<IOrderingService, OrderingService>() ...
|
||||
|
||||
//Add CampaignService typed client (Service Agent)
|
||||
//services.AddHttpClient<ICampaignService, CampaignService>() ...
|
||||
|
||||
//Add LocationService typed client (Service Agent)
|
||||
//services.AddHttpClient<ILocationService, LocationService>() ...
|
||||
|
||||
//Add IdentityParser typed client (Service Agent)
|
||||
//services.AddHttpClient<IIdentityParser, IdentityParser>() ...
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// (CDLTLL) TEMPORAL COMMENT: The following Typed-Clients (Service-Agents) have to be coded as BasketService by using AddHttpClient<>(), right?
|
||||
// This code will be deleted
|
||||
services.AddTransient<ICatalogService, CatalogService>();
|
||||
services.AddTransient<IOrderingService, OrderingService>();
|
||||
|
||||
services.AddTransient<ICampaignService, CampaignService>();
|
||||
services.AddTransient<ILocationService, LocationService>();
|
||||
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// TEMPORAL COMMENT: This code will be deleted when using HttpClientFactory, right?
|
||||
if (configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
|
||||
{
|
||||
services.AddSingleton<IResilientHttpClientFactory, ResilientHttpClientFactory>(sp =>
|
||||
@ -214,6 +279,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
||||
{
|
||||
services.AddSingleton<IHttpClient, StandardHttpClient>();
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
return services;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user