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:
Cesar De la Torre Llorente 2018-05-16 20:30:05 -07:00
parent 18e6dc4afb
commit dc5de83747
2 changed files with 125 additions and 45 deletions

View File

@ -1,5 +1,7 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Polly; using Polly;
using Polly.CircuitBreaker;
using Polly.Retry;
using System; using System;
using System.Net.Http; using System.Net.Http;
@ -7,8 +9,13 @@ namespace WebMVC.Infrastructure
{ {
public class HttpClientDefaultPolicies public class HttpClientDefaultPolicies
{ {
const int RETRY_COUNT = 6; //Config for Retries with exponential backoff policy
const int EXCEPTIONS_ALLOWED_BEFORE_CIRCUIT_BREAKER = 5; 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; private readonly ILogger<HttpClientDefaultPolicies> _logger;
@ -17,14 +24,15 @@ namespace WebMVC.Infrastructure
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
} }
public Policy GetWaitAndRetryPolicy() public IAsyncPolicy<HttpResponseMessage> GetWaitAndRetryPolicy()
{ {
return Policy.Handle<HttpRequestException>() RetryPolicy retryPolicy =
Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync( .WaitAndRetryAsync(
// number of retries // Maximum number of retries
RETRY_COUNT, MAX_RETRIES,
// exponential backofff // exponential backofff (2sg, 4sg, 8sg, 16sg, 32sg. etc.)
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), retryAttempt => TimeSpan.FromSeconds(Math.Pow(SECONDS_BASE_FOR_EXPONENTIAL_BACKOFF, retryAttempt)),
// on retry // on retry
(exception, timeSpan, retryCount, context) => (exception, timeSpan, retryCount, context) =>
{ {
@ -35,26 +43,32 @@ namespace WebMVC.Infrastructure
_logger.LogWarning(msg); _logger.LogWarning(msg);
_logger.LogDebug(msg); _logger.LogDebug(msg);
}); });
return retryPolicy.AsAsyncPolicy<HttpResponseMessage>();
} }
public Policy GetCircuitBreakerPolicy() public IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{ {
return Policy.Handle<HttpRequestException>() CircuitBreakerPolicy circuitBreakerPolicy =
Policy.Handle<HttpRequestException>()
.CircuitBreakerAsync( .CircuitBreakerAsync(
// number of exceptions before breaking circuit // Number of exceptions before breaking the circuit
EXCEPTIONS_ALLOWED_BEFORE_CIRCUIT_BREAKER, EXCEPTIONS_ALLOWED_BEFORE_CIRCUIT_BREAKES,
// time circuit opened before retry // Duration of break
TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(DURATION_OF_BREAK_IN_MINUTES),
(exception, duration) => (exception, duration) =>
{ {
// on circuit opened // On circuit opened (Circuit is broken)
_logger.LogTrace("Circuit breaker opened"); _logger.LogTrace("Circuit has been broken");
}, },
() => () =>
{ {
// on circuit closed // on circuit closed
_logger.LogTrace("Circuit breaker reset"); _logger.LogTrace("Circuit has been reset");
}); });
return circuitBreakerPolicy.AsAsyncPolicy<HttpResponseMessage>();
} }
} }
} }

View File

@ -15,7 +15,11 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Polly; using Polly;
using Polly.CircuitBreaker;
using Polly.Extensions.Http;
using Polly.Registry; using Polly.Registry;
using Polly.Retry;
using Polly.Timeout;
using StackExchange.Redis; using StackExchange.Redis;
using System; using System;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
@ -35,13 +39,13 @@ namespace Microsoft.eShopOnContainers.WebMVC
public IConfiguration Configuration { get; } 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) public void ConfigureServices(IServiceCollection services)
{ {
services.AddAppInsight(Configuration) services.AddAppInsight(Configuration)
.AddHealthChecks(Configuration) .AddHealthChecks(Configuration)
.AddCustomMvc(Configuration) .AddCustomMvc(Configuration)
.AddCustomApplicationServices(Configuration) .AddHttpClientServices(Configuration)
.AddCustomAuthentication(Configuration); .AddCustomAuthentication(Configuration);
} }
@ -165,28 +169,89 @@ namespace Microsoft.eShopOnContainers.WebMVC
return services; 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>(); 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>(); services.AddSingleton<HttpClientDefaultPolicies>();
var defaultPolicies = services.BuildServiceProvider().GetService<HttpClientDefaultPolicies>(); var defaultPolicies = services.BuildServiceProvider().GetService<HttpClientDefaultPolicies>();
var registry = services.AddPolicyRegistry(); // Create a Polly-Policy-Registry with the by default policies for resilient Http requests
registry.Add("WaitAndRetry", defaultPolicies.GetWaitAndRetryPolicy()); var pollyPolicyRegistry = services.AddPolicyRegistry();
registry.Add("CircuitBreaker", defaultPolicies.GetCircuitBreakerPolicy());
//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>() services.AddHttpClient<IBasketService, BasketService>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>() .AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>() //Additional Authentication-Delegating-Handler to add the OAuth-Bearer-token to the Http headers
.AddPolicyHandlerFromRegistry("WaitAndRetry") .AddPolicyHandlerFromRegistry("DefaultRetriesWithExponentialBackoff")
.AddPolicyHandlerFromRegistry("CircuitBreaker"); .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<ICatalogService, CatalogService>();
services.AddTransient<IOrderingService, OrderingService>(); services.AddTransient<IOrderingService, OrderingService>();
services.AddTransient<ICampaignService, CampaignService>(); services.AddTransient<ICampaignService, CampaignService>();
services.AddTransient<ILocationService, LocationService>(); services.AddTransient<ILocationService, LocationService>();
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>(); services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TEMPORAL COMMENT: This code will be deleted when using HttpClientFactory, right?
if (configuration.GetValue<string>("UseResilientHttp") == bool.TrueString) if (configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
{ {
services.AddSingleton<IResilientHttpClientFactory, ResilientHttpClientFactory>(sp => services.AddSingleton<IResilientHttpClientFactory, ResilientHttpClientFactory>(sp =>
@ -214,6 +279,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
{ {
services.AddSingleton<IHttpClient, StandardHttpClient>(); services.AddSingleton<IHttpClient, StandardHttpClient>();
} }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return services; return services;
} }