Résilience avec Polly.Net (partie 2)

Niveau :

11 minutes de lecture

Vous pouvez retrouver la première partie de cet article ici : Résilience avec Polly.Net (partie 1).

Dans cet article, vous allez apprendre à rendre votre application résiliente avec Visual Studio et la célèbre bibliothèque Polly ainsi :

  • Création d’une stratégie de type RETRY avec temporisation
  • Création d’une stratégie CIRCUITBREAKER
  • Etat de la stratégie CIRCUITBREAKER

Création d’une stratégie de type RETRY avec temporisation

Dans le fichier RequestPolicies ajoutez la stratégie de type WAITANDRETRY. Cette stratégie permet d’exécuter une ou plusieurs nouvelles tentatives mais en ajoutant une temporisation avant l’appel suivant. Le nombre de tentatives et la temporisation sont paramétrables :

using Polly;
using Polly.Retry;

namespace ApiResilienceWithPolly.Policies
{
    public class RequestPolicies
    {
        
        public AsyncRetryPolicy<HttpResponseMessage> HttpRetryPolicy { get; }
        public AsyncRetryPolicy<HttpResponseMessage> HttpWaitAndRetryPolicy { get; }

        public RequestPolicies()
        {
            int retryCount = 5;
            
            HttpRetryPolicy = Policy.HandleResult<HttpResponseMessage>(result=> !result.IsSuccessStatusCode).RetryAsync(retryCount);

            HttpWaitAndRetryPolicy = Policy.HandleResult<HttpResponseMessage>(result => !result.IsSuccessStatusCode)
                                     .WaitAndRetryAsync(retryCount,retryAttempt=>TimeSpan.FromSeconds(2));
        }
    }
}

Cette stratégie est paramétrée pour relancer l’appel au bout de 2 secondes et pour un maximum de 5 fois. Appliquez là à la méthode Get du controller RequestController comme ci-dessous :

public class RequestController : ControllerBase
    {
        private readonly RequestPolicies _requestPolicies;

        public RequestController(RequestPolicies requestPolicies)
        {
            _requestPolicies = requestPolicies;
        }

        [HttpGet]
        public async Task<ActionResult> CallApiExterne()
        {
            var client = new HttpClient();

            //var response = await client.GetAsync("https://localhost:7286/WeatherForecast/50");
            
            var response = await _requestPolicies.HttpWaitAndRetryPolicy.ExecuteAsync(() =>
             client.GetAsync("https://localhost:7286/WeatherForecast/50"));

            return response.IsSuccessStatusCode ? Ok() : StatusCode(StatusCodes.Status500InternalServerError);
        }
    }

Exécutez les projets et constatez la temporisation de deux secondes lors des erreurs.

Création d’une stratégie CIRCUITBREAKER

Cette stratégie permet de prévenir une action qui serait obligatoirement un échec et surtout de bloquer les futurs appels (qui seraient eux aussi en échec). Pour résumer, pourquoi continuer à laisser un client faire des appels à une opération que l’on sait être en échec ?

Comme il n’est pas évident de l’expliquer par des mots, un simple exemple est souvent plus parlant :

HttpCircuitBreakerPolicy = Policy.HandleResult<HttpResponseMessage>(result => !result.IsSuccessStatusCode)
                                     .CircuitBreakerAsync(1, TimeSpan.FromSeconds(20));

Dans cet exemple, nous indiquons que si 1 appel est en erreur, alors nous coupons le circuit d’appel pendant 20 secondes. L’exemple n’est certainement pas un cas utile pour une stratégie CIRCUITBREAKER mais permet de bien comprendre le principe.

Lorsque le statut du circuit est CLOSED, tout fonctionne correctement. En cas d’erreurs (dans notre exemple : 1), le circuit passe en OPEN est applique la stratégie choisie pendant le temps demandé (dans notre exemple 20 secondes). Une fois le temps écoulé, le circuit vérifie si pas d’erreur et revient au statut CLOSED ou OPEN suivant le cas.

Appliquez la stratégie CIRCUITBREAKER à la méthode Get du controller RequestController comme ci-dessous :

public class RequestController : ControllerBase
    {
        private readonly RequestPolicies _requestPolicies;

        public RequestController(RequestPolicies requestPolicies)
        {
            _requestPolicies = requestPolicies;
        }

        [HttpGet]
        public async Task<ActionResult> CallApiExterne()
        {
            var client = new HttpClient();

            //var response = await client.GetAsync("https://localhost:7286/WeatherForecast/50");

            //var response = await _requestPolicies.HttpWaitAndRetryPolicy.ExecuteAsync(() =>
            // client.GetAsync("https://localhost:7286/WeatherForecast/50"));

            var response = await _requestPolicies.HttpCircuitBreakerPolicy.ExecuteAsync(() =>
             client.GetAsync("https://localhost:7286/WeatherForecast/50"));

            return response.IsSuccessStatusCode ? Ok() : StatusCode(StatusCodes.Status500InternalServerError);
        }
    }

Exécutez les projets et en cas d’erreurs, refaite immédiatement un appel à la méthode. Vous constatez alors une erreur de type Polly.CircuitBreaker.BrokenCircuitException et surtout, vous constatez que l’appel n’est pas exécuté côté ApiRessourcesExternes pendant 20 secondes :

Etat de la stratégie CIRCUITBREAKER

Allons un peu plus loin en rajoutant deux nouvelles méthodes à notre stratégie pour contrôler l’état de notre service comme ci-dessous :

public RequestPolicies()
        {
            int retryCount = 5;
            
            HttpRetryPolicy = Policy.HandleResult<HttpResponseMessage>(result=> !result.IsSuccessStatusCode).RetryAsync(retryCount);

            HttpWaitAndRetryPolicy = Policy.HandleResult<HttpResponseMessage>(result => !result.IsSuccessStatusCode)
                                     .WaitAndRetryAsync(retryCount,retryAttempt=>TimeSpan.FromSeconds(2));

            HttpCircuitBreakerPolicy = Policy.HandleResult<HttpResponseMessage>(result => !result.IsSuccessStatusCode)
                                     .CircuitBreakerAsync(1, TimeSpan.FromSeconds(20));

            HttpCircuitBreakerWithCheckStatePolicy = Policy.HandleResult<HttpResponseMessage>(result => !result.IsSuccessStatusCode)
                                     .CircuitBreakerAsync(1, TimeSpan.FromSeconds(20), CircuitToStateOpen,CircuitToStateClosed );

        }

        private void CircuitToStateOpen(DelegateResult<HttpResponseMessage> result, TimeSpan ts)
        {
            Console.WriteLine("Argh, Circuit en panne !");
        }

        private void CircuitToStateClosed()
        {
            Console.WriteLine("Ouf, Circuit revenu à la normale !");
        }

Appliquez cette stratégie à la méthode Get et exécutez à nouveaux les projets :

Vous pouvez ainsi avoir l’état de votre stratégie CIRCUITBREAKER et ainsi afficher par exemple le temps restant avant le timeout ou autre.

Laisser un commentaire