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);
        }
Example #2
0
 public void Cleanup()
 {
     DemoHelper.Reset();
 }
Example #3
0
        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);
        }