Refactoring resilience implementation in order to use Polly directly when creating the resilience policies

This commit is contained in:
dsanz 2017-04-07 12:48:22 +02:00
parent e05cbfa809
commit 0c317d56f3
7 changed files with 77 additions and 141 deletions

View File

@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http.Policies
{
internal class CircuitBreakerPolicy : ResiliencePolicy
{
public CircuitBreakerPolicy(int exceptionsAllowedBeforeBreaking, int durationOfBreakInMinutes)
{
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
DurationOfBreakInMinutes = durationOfBreakInMinutes;
}
public int ExceptionsAllowedBeforeBreaking { get; }
public int DurationOfBreakInMinutes { get; }
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http.Policies
{
internal class RetryPolicy : ResiliencePolicy
{
public RetryPolicy(int retries, int backoffSeconds, bool exponentialBackoff)
{
Retries = retries;
BackoffSeconds = backoffSeconds;
ExponentialBackoff = exponentialBackoff;
}
public int Retries { get; }
public int BackoffSeconds { get; }
public bool ExponentialBackoff { get; }
}
}

View File

@ -1,20 +0,0 @@
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http.Policies;
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
{
public static class ResiliencePolicyFactory
{
public static ResiliencePolicy CreateRetryPolicy(int retries, int backoffSeconds, bool exponentialBackoff)
{
return new RetryPolicy(retries, backoffSeconds, exponentialBackoff);
}
public static ResiliencePolicy CreateCiscuitBreakerPolicy(int exceptionsAllowedBeforeBreaking, int durationOfBreakInMinutes)
{
return new CircuitBreakerPolicy(exceptionsAllowedBeforeBreaking, durationOfBreakInMinutes);
}
}
}

View File

@ -1,5 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http.Policies; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Polly; using Polly;
using Polly.Wrap; using Polly.Wrap;
@ -23,84 +22,14 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
private ILogger<ResilientHttpClient> _logger; private ILogger<ResilientHttpClient> _logger;
public HttpClient Inst => _client; public HttpClient Inst => _client;
public ResilientHttpClient(List<Policy> policies, ILogger<ResilientHttpClient> logger) public ResilientHttpClient(Policy[] policies, ILogger<ResilientHttpClient> logger)
{ {
_client = new HttpClient(); _client = new HttpClient();
_logger = logger; _logger = logger;
// Add Policies to be applied // Add Policies to be applied
_policyWrapper = Policy.WrapAsync(policies.ToArray()); _policyWrapper = Policy.WrapAsync(policies);
} }
public ResilientHttpClient(List<ResiliencePolicy> policies, ILogger<ResilientHttpClient> logger)
{
_client = new HttpClient();
_logger = logger;
// Add Policies to be applied
_policyWrapper = Policy.WrapAsync(GeneratePolicies(policies));
}
private Policy[] GeneratePolicies(IList<ResiliencePolicy> policies)
{
var pollyPolicies = new List<Policy>();
foreach (var policy in policies)
{
switch (policy)
{
case RetryPolicy retryPolicy:
pollyPolicies.Add(
CreateRetryPolicy(
retryPolicy.Retries,
retryPolicy.BackoffSeconds,
retryPolicy.ExponentialBackoff));
break;
case CircuitBreakerPolicy circuitPolicy:
pollyPolicies.Add(
CreateCircuitBreakerPolicy(
circuitPolicy.ExceptionsAllowedBeforeBreaking,
circuitPolicy.DurationOfBreakInMinutes));
break;
}
}
return pollyPolicies.ToArray();
}
private Policy CreateRetryPolicy(int retries, int backoffSeconds, bool exponentialBackoff) =>
Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync(
retries,
retryAttempt => exponentialBackoff ? TimeSpan.FromSeconds(Math.Pow(backoffSeconds, retryAttempt)) : TimeSpan.FromSeconds(backoffSeconds),
(exception, timeSpan, retryCount, context) =>
{
var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
$"of {context.PolicyKey} " +
$"at {context.ExecutionKey}, " +
$"due to: {exception}.";
_logger.LogWarning(msg);
_logger.LogDebug(msg);
}
);
private Policy CreateCircuitBreakerPolicy(int exceptionsAllowedBeforeBreaking, int durationOfBreakInMinutes) =>
Policy.Handle<HttpRequestException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking,
TimeSpan.FromMinutes(durationOfBreakInMinutes),
(exception, duration) =>
{
// on circuit opened
_logger.LogTrace("Circuit breaker opened");
},
() =>
{
// on circuit closed
_logger.LogTrace("Circuit breaker reset");
}
);
public Task<string> GetStringAsync(string uri) => public Task<string> GetStringAsync(string uri) =>
HttpInvoker(() => HttpInvoker(() =>

View File

@ -0,0 +1,10 @@
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using System;
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
{
public interface IResilientHttpClientFactory
{
ResilientHttpClient CreateResilientHttpClient();
}
}

View File

@ -0,0 +1,59 @@
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Polly;
using System.Net.Http;
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
{
public class ResilientHttpClientFactory : IResilientHttpClientFactory
{
private readonly ILogger<ResilientHttpClient> _logger;
public ResilientHttpClientFactory(ILogger<ResilientHttpClient> logger)
=>_logger = logger;
public ResilientHttpClient CreateResilientHttpClient()
=> new ResilientHttpClient(CreatePolicies(), _logger);
private Policy[] CreatePolicies()
=> new Policy[]
{
Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync(
// number of retries
6,
// 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.ExecutionKey}, " +
$"due to: {exception}.";
_logger.LogWarning(msg);
_logger.LogDebug(msg);
}),
Policy.Handle<HttpRequestException>()
.CircuitBreakerAsync(
// number of exceptions before breaking circuit
5,
// 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");
})};
}
}

View File

@ -16,6 +16,7 @@ using System.Threading;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http; using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.Infrastructure;
namespace Microsoft.eShopOnContainers.WebMVC namespace Microsoft.eShopOnContainers.WebMVC
{ {
@ -63,19 +64,14 @@ namespace Microsoft.eShopOnContainers.WebMVC
if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString) if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
{ {
services.AddSingleton( services.AddTransient<IResilientHttpClientFactory, ResilientHttpClientFactory>();
new List<ResiliencePolicy> services.AddTransient<IHttpClient, ResilientHttpClient>(sp => sp.GetService<IResilientHttpClientFactory>().CreateResilientHttpClient());
{
ResiliencePolicyFactory.CreateRetryPolicy(6, 2, true),
ResiliencePolicyFactory.CreateCiscuitBreakerPolicy(5, 1)
});
services.AddTransient<IHttpClient, ResilientHttpClient>();
} }
else else
{ {
services.AddTransient<IHttpClient, StandardHttpClient>(); services.AddTransient<IHttpClient, StandardHttpClient>();
} }
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)