public async Task PolicyRegistry_BasicExample() { // typically this stuff would go in Startup.ConfigureServices IServiceCollection services = new ServiceCollection(); services.AddPolicyRegistry(PolicyRegistryFactory.GetPolicyRegistry()); // typically you would inject this into a class via constructor var serviceProvider = services.BuildServiceProvider(); var registry = serviceProvider.GetService<IReadOnlyPolicyRegistry<string>>(); // customer service contains a retry to this will succeed var customerServicePolicy = registry.Get<IAsyncPolicy<HttpResponseMessage>>("CustomerServicePolicy"); var response = await customerServicePolicy.ExecuteAsync(ct => DemoHelper.DemoClient.GetAsync("api/demo/error?failures=1", ct), CancellationToken.None); var responseMessage = await response.Content.ReadAsStringAsync(); Assert.AreEqual("Success!", responseMessage); DemoHelper.Reset(); // product service does not contain a retry, so this will fail (return default) var productServicePolicy = registry.Get<IAsyncPolicy<HttpResponseMessage>>("ProductServicePolicy"); response = await productServicePolicy.ExecuteAsync( ct => DemoHelper.DemoClient.GetAsync("api/demo/error?failures=1", ct), CancellationToken.None); responseMessage = await response.Content.ReadAsStringAsync(); Assert.AreEqual("Default!", responseMessage); }
public void Cleanup() { DemoHelper.Reset(); }
public async Task AddResiliencyToHttpCall() { ///////////////////////// OUTER TIMEOUT POLICY ///////////////////////// // for sync code, you should replace this by setting the timeout on the HttpClient IAsyncPolicy <HttpResponseMessage> outerTimeoutPolicy = Policy .TimeoutAsync <HttpResponseMessage>(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic, onTimeoutAsync: PolicyLoggingHelper.LogTimeoutAsync); ///////////////////////// RETRY POLICY ///////////////////////// // we are retrying on inner timeout, this might be a bad idea for many scenerios // you never want to retry on a circuit breaker exception IAsyncPolicy <HttpResponseMessage> retryPolicy = HttpPolicyExtensions .HandleTransientHttpError() .Or <TimeoutRejectedException>() .WaitAndRetryAsync(1, retryAttempt => TimeSpan.FromMilliseconds(500), onRetryAsync: PolicyLoggingHelper.LogWaitAndRetryAsync); ///////////////////////// CIRCUIT BREAKER POLICY ///////////////////////// // typical duration of break should be 30-60 seconds. Using 5 for this demo to make demo run faster IAsyncPolicy <HttpResponseMessage> circuitBreakerAsyncPolicy = Policy .HandleResult <HttpResponseMessage>(resp => !resp.IsSuccessStatusCode) // consider if you want to ignore 404s .Or <Exception>() .CircuitBreakerAsync(5, TimeSpan.FromSeconds(5), onBreak: PolicyLoggingHelper.LogCircuitBroken, onReset: PolicyLoggingHelper.LogCircuitReset) .WithPolicyKey(nameof(circuitBreakerAsyncPolicy)); // pointer to the circuit breaker so we can read/update its state as needed (haven't figured out a better way to do this) // this is EXTREMELY useful for resetting between unit tests (expose a reset method to allow callers to reset your circuit breaker) ICircuitBreakerPolicy <HttpResponseMessage> circuitBreaker = (ICircuitBreakerPolicy <HttpResponseMessage>)circuitBreakerAsyncPolicy; ///////////////////////// INNER TIMOUT POLICY (Optional) ///////////////////////// // inner timeout is totally optional and depends on use case. // The outer timeout is the most important for requests with callers waiting // Sometimes you might want an inner timeout slightly lower than the outer just so the circuit breaker will break on too many timeouts IAsyncPolicy <HttpResponseMessage> innerTimeoutPolicy = Policy .TimeoutAsync <HttpResponseMessage>(TimeSpan.FromSeconds(3), TimeoutStrategy.Optimistic, onTimeoutAsync: PolicyLoggingHelper.LogTimeoutAsync); ///////////////////////// POLICY WRAP ///////////////////////// // wrap policies from outer to inner var commonResiliencyPolicy = Policy .WrapAsync <HttpResponseMessage>(outerTimeoutPolicy, retryPolicy, circuitBreakerAsyncPolicy, innerTimeoutPolicy); ///////////////////////// FALLBACK POLICY (Wraps wrapped policy) ///////////////////////// // alternative would be to wrap ExecuteAsync in try/catch and return default from there // I am wrapping the above resiliency policy to show that you can wrap a wrapped policy inside another policy // Fallback is a good use case for this since it can vary a lot between different calls IAsyncPolicy <HttpResponseMessage> resiliencyPolicyWithFallback = HttpPolicyExtensions .HandleTransientHttpError() .Or <TimeoutRejectedException>() .Or <BrokenCircuitException>() .FallbackAsync(new HttpResponseMessage { // fetch default values or execute whatever behavior you want to fall back to Content = new StringContent("Default!") }, onFallbackAsync: PolicyLoggingHelper.LogFallbackAsync) .WrapAsync(commonResiliencyPolicy); // verify retry on error var response = await resiliencyPolicyWithFallback.ExecuteAsync((ctx, ct) => DemoHelper.DemoClient.GetAsync("api/demo/error?failures=1", ct), new Context($"{nameof(AddResiliencyToHttpCall)}_1"), CancellationToken.None); Assert.IsTrue(response.IsSuccessStatusCode); // verify retry on client timeout response = await resiliencyPolicyWithFallback.ExecuteAsync((ctx, ct) => DemoHelper.DemoClient.GetAsync("api/demo/slow?failures=1", ct), new Context($"{nameof(AddResiliencyToHttpCall)}_2"), CancellationToken.None); Assert.IsTrue(response.IsSuccessStatusCode); // verify default if every call times out DemoHelper.Reset(); response = await resiliencyPolicyWithFallback.ExecuteAsync((ctx, ct) => DemoHelper.DemoClient.GetAsync("api/demo/slow", ct), new Context($"{nameof(AddResiliencyToHttpCall)}_3"), CancellationToken.None); var result = await response.Content.ReadAsStringAsync(); Assert.AreEqual("Default!", result); // verify circuit broken on errors for (int i = 0; i < 6; i++) { response = await resiliencyPolicyWithFallback.ExecuteAsync((ctx, ct) => DemoHelper.DemoClient.GetAsync("api/demo/error", ct), new Context($"{nameof(AddResiliencyToHttpCall)}_4"), CancellationToken.None); result = await response.Content.ReadAsStringAsync(); Assert.AreEqual("Default!", result); } Assert.AreEqual(CircuitState.Open, circuitBreaker.CircuitState); // let circuit breaker reset (goes to 1/2 open after 5 seconds) await Task.Delay(5100); Assert.AreEqual(CircuitState.HalfOpen, circuitBreaker.CircuitState); // verify circuit breaker is back open await commonResiliencyPolicy.ExecuteAsync((ctx, ct) => DemoHelper.DemoClient.GetAsync("api/demo/success", ct), new Context($"{nameof(AddResiliencyToHttpCall)}_5"), CancellationToken.None); Assert.AreEqual(CircuitState.Closed, circuitBreaker.CircuitState); }