Resiliência em Aplicações Asp.Net

Por Jonathan Amaral

Nos dias de hoje, garantir a resiliência das nossas aplicações é essencial. Em um mundo onde os sistemas precisam estar disponíveis 100% do tempo, é necessário adotar práticas que permitam que nossos serviços recuperem-se rapidamente de falhas e continuem operando. Neste post, exploraremos as políticas de resiliência em aplicações .NET, utilizando a biblioteca Polly para implementar estratégias eficazes.

O que é Resiliência?

Resiliência é a capacidade de um sistema retornar ao seu estado normal após sofrer algum colapso ou interrupção. Isso é crucial para evitar a perda de produtividade e, por consequência, de receita para o negócio. Partimos da premissa de que tudo pode falhar e, por isso, é preciso preparar nossas aplicações para lidar com esses desafios.

Introduzindo Polly

Polly é uma biblioteca popular para tratamento de falhas transitórias e resiliência em .NET. Ela fornece uma estrutura flexível para definir políticas como tentativas de repetição, timeouts, fallback e circuit breakers. Vamos explorar cada uma dessas políticas e ver exemplos práticos de implementação.

Retry (Repetição)

A política de Retry (ou Retentativa) permite que a aplicação tente repetir uma operação falha, reduzindo a probabilidade de erro ao tentar novamente.

Exemplo de implementação:


Policy.Handle<YourException>()
.WaitAndRetryAsync(
    retryCount: 3,
    sleepDurationProvider: retryAttempt => 
        TimeSpan.FromMilliseconds(settings.RetryInterval * Math.Pow(2, retryAttempt)),
    onRetry: (httpResponse, timeSpan, count, context) =>
    { 
        RetryLogRequest(timeSpan, count);
    });
  • RetryCount: número de tentativas de repetição.
  • SleepDurationProvider: define o tempo de espera antes de cada nova tentativa.
  • OnRetry: ação chamada a cada nova tentativa, permitindo o log ou manipulação adicional.

Timeout (Tempo Limite)

O Timeout define um tempo máximo de espera para uma operação, ajudando a evitar que chamadas fiquem "presas" indefinidamente.

Exemplo de implementação:


Policy.TimeoutAsync<HttpResponseMessage>(
    timeout: TimeSpan.FromMilliseconds(settings.TimeoutInMilliseconds),
    timeoutStrategy: TimeoutStrategy.Optimistic,
    onTimeoutAsync: (context, timespan, _, exc) =>
    {
        TimeoutLogRequest(timespan);
        return Task.CompletedTask;
    });
  • Timeout: tempo limite para a operação.
  • TimeoutStrategy: define a estratégia, podendo ser "Optimistic" (cancelamento via CancellationToken) ou "Pessimistic" (bloqueia até o término ou timeout).
  • OnTimeoutAsync: ação chamada em caso de timeout.

Fallback (Alternativa)

O Fallback fornece uma resposta alternativa para operações que falhem, garantindo que o sistema continue funcionando.

Exemplo de implementação:


using var defaultFallBack = new HttpResponseMessage(HttpStatusCode.FailedDependency);
defaultFallBack.RequestMessage = new HttpRequestMessage();

Policy<HttpResponseMessage>
    .Handle<HttpRequestException>()
    .OrResult(result => result.StatusCode == HttpStatusCode.ServiceUnavailable)
    .FallbackAsync(fallbackValue: defaultFallBack, onFallbackAsync: (http, context) =>
    {
        FallbackLogRequest(http.Result.StatusCode, defaultFallBack.StatusCode);
        return Task.CompletedTask;
    });
  • FallbackValue: valor substituto a ser usado em caso de falha.
  • OnFallbackAsync: ação executada antes de retornar o valor alternativo.

Circuit Breaker (Disjuntor)

O Circuit Breaker impede tentativas contínuas quando um serviço está com problemas, interrompendo as operações por um tempo para evitar sobrecarga.

Exemplo de implementação:


HttpPolicyExtensions
    .HandleTransientHttpError()
    .AdvancedCircuitBreakerAsync(
        failureThreshold: 0.25,
        samplingDuration: TimeSpan.FromSeconds(60),
        minimumThroughput: 7,
        durationOfBreak: TimeSpan.FromSeconds(30),
        onBreak: (ex, timeSpan) => CircuitBreakerLogRequest(CircuitState.Open),
        onReset: () => CircuitBreakerLogRequest(CircuitState.Closed),
        onHalfOpen: () => CircuitBreakerLogRequest(CircuitState.HalfOpen));
  • FailureThreshold: limite de falha para interromper o circuito.
  • SamplingDuration: janela de tempo para medir a taxa de falhas.
  • MinimumThroughput: mínimo de requisições para que o disjuntor entre em ação.
  • DurationOfBreak: tempo que o circuito permanecerá aberto.

Demonstração Prática

Para ver essas implementações em ação, acesse o repositório StoneBreakeven no GitHub, onde apresento exemplos práticos dessas políticas.


Essas práticas são essenciais para garantir que suas aplicações ASP.NET sejam resilientes, mantendo a continuidade dos serviços mesmo diante de falhas inesperadas.

Comentários

Postagens mais visitadas deste blog

Usando o Padrão CQS (Command Query Separation) no ASP.NET

Diário de bordo: como a autoavaliação pode transformar sua carreira