tutoriales.com

C#: Construyendo APIs Resilientes con Polly y HttpClients para Manejo de Fallos Avanzado

Este tutorial te guiará a través de la implementación de políticas de resiliencia en tus aplicaciones C# utilizando la biblioteca Polly junto con HttpClient. Aprenderás a configurar estrategias de reintento, disyuntor y timeout para construir APIs y servicios que puedan recuperarse elegantemente de fallos transitorios y protegerse contra la sobrecarga de dependencias externas. Construye sistemas más robustos y tolerantes a fallos.

Intermedio25 min de lectura14 views
Reportar error

🚀 Introducción a la Resiliencia en Aplicaciones C#

En el mundo del desarrollo de software moderno, especialmente en arquitecturas de microservicios o sistemas distribuidos, la resiliencia no es solo un concepto deseable, sino una necesidad crítica. Las aplicaciones rara vez operan en un entorno perfecto; las dependencias externas (otras APIs, bases de datos, servicios de terceros) pueden experimentar latencia, interrupciones o fallos temporales. Si nuestra aplicación no está preparada para manejar estos escenarios, un pequeño fallo en un servicio dependiente puede desencadenar una cascada de errores, resultando en una indisponibilidad completa de nuestro sistema.

Aquí es donde entra en juego la resiliencia. Una aplicación resiliente es aquella que puede mantener su funcionalidad incluso frente a fallos parciales o condiciones adversas. En C#, una de las herramientas más poderosas para lograr esta resiliencia es la biblioteca Polly.

¿Qué es Polly? 🤔

Polly es una biblioteca de .NET que permite definir y encadenar políticas de resiliencia y manejo de fallos de manera fluida y declarativa. Proporciona estrategias para gestionar una amplia gama de escenarios de fallo, tales como:

  • Reintentos (Retries): Intentar de nuevo una operación que falló.
  • Disyuntores (Circuit Breakers): Detener las llamadas a un servicio que está fallando repetidamente para darle tiempo a recuperarse.
  • Timeouts: Limitar el tiempo de ejecución de una operación.
  • Caché: Almacenar en caché resultados de operaciones para evitar llamadas redundantes.
  • Fallbacks: Proporcionar un valor alternativo cuando una operación falla.
  • Bulkhead: Limitar el número de operaciones concurrentes para aislar fallos.

En este tutorial, nos centraremos en las políticas de reintento, disyuntor y timeout, aplicándolas específicamente a llamadas HTTP realizadas con HttpClient, un componente fundamental para la comunicación entre servicios en C#.

🛠️ Configuración del Entorno de Desarrollo

Antes de sumergirnos en la implementación, necesitamos configurar un proyecto básico de .NET.

📦 Requisitos Previos

  • .NET SDK (versión 6.0 o superior recomendada)
  • Un editor de código (Visual Studio, VS Code, Rider)

🆕 Crear un Nuevo Proyecto

Vamos a crear una aplicación de consola para demostrar los conceptos. Abre tu terminal o línea de comandos y ejecuta:

dotnet new console -n PollyHttpClientDemo
cd PollyHttpClientDemo

⬇️ Instalar Paquetes NuGet Necesarios

Para trabajar con Polly y HttpClient de forma integrada, necesitaremos algunos paquetes:

  • Polly: La biblioteca principal de Polly.
  • Microsoft.Extensions.Http.Polly: La integración de Polly con IHttpClientFactory para una configuración sencilla.

Ejecuta los siguientes comandos en tu terminal:

dotnet add package Polly
dotnet add package Microsoft.Extensions.Http.Polly
dotnet add package Microsoft.Extensions.Hosting

Microsoft.Extensions.Hosting es necesario para configurar el IHttpClientFactory usando la inyección de dependencias.


🔄 Reintentos con Polly y HttpClient

La política de reintentos es fundamental para manejar fallos transitorios. Imagina un servicio externo que ocasionalmente devuelve un error 500 debido a una sobrecarga momentánea. Un reintento inteligente puede permitir que la operación tenga éxito en el segundo o tercer intento.

Tipos de Reintentos ✨

Polly ofrece varios tipos de reintentos:

  • Reintento simple: Intenta la operación un número fijo de veces con un retraso fijo.
  • Reintento con retroceso exponencial: Aumenta el retraso entre reintentos exponencialmente, lo cual es ideal para evitar sobrecargar un servicio que se está recuperando.

Vamos a configurar un reintento con retroceso exponencial.

Implementación del Reintento 🎯

Modifica tu archivo Program.cs para configurar un HttpClient que use una política de reintento.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Polly;
using Polly.Extensions.Http;

namespace PollyHttpClientDemo
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            var httpClientFactory = host.Services.GetRequiredService<IHttpClientFactory>();
            var myApiService = new MyApiService(httpClientFactory);

            Console.WriteLine("\n--- Demostración de Reintentos ---");
            await myApiService.GetDataWithRetries("https://httpstat.us/500"); // Simulamos un 500 Internal Server Error
            await myApiService.GetDataWithRetries("https://httpstat.us/200"); // Solicitud exitosa

            Console.WriteLine("\n--- Demostración de Disyuntores ---");
            await myApiService.GetDataWithCircuitBreaker("https://httpstat.us/500");
            await Task.Delay(500); // Espera un poco
            await myApiService.GetDataWithCircuitBreaker("https://httpstat.us/500");
            await Task.Delay(500); // Espera un poco
            await myApiService.GetDataWithCircuitBreaker("https://httpstat.us/500"); // Este debería fallar rápido (circuito abierto)
            await Task.Delay(TimeSpan.FromSeconds(6)); // Espera a que el disyuntor se restablezca (5 segundos + margen)
            Console.WriteLine("Intentando de nuevo después de disyuntor...");
            await myApiService.GetDataWithCircuitBreaker("https://httpstat.us/200"); // Ahora debería pasar

            Console.WriteLine("\n--- Demostración de Timeouts ---");
            await myApiService.GetDataWithTimeout("https://httpstat.us/200?sleep=6000"); // Más de 5 segundos, debería expirar
            await myApiService.GetDataWithTimeout("https://httpstat.us/200?sleep=1000"); // Menos de 5 segundos, debería pasar

            // await host.RunAsync(); // No necesario para este demo de consola simple
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHttpClient("MyApiClient")
                        .AddPolicyHandler(GetRetryPolicy())
                        .AddPolicyHandler(GetCircuitBreakerPolicy())
                        .AddPolicyHandler(GetTimeoutPolicy()); // Encadenar políticas

                    services.AddTransient<MyApiService>();
                });

        static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
        {
            return HttpPolicyExtensions
                .HandleTransientHttpError() // Maneja 408, 5xx y Network failures
                .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) // O también si es 404
                .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 3 reintentos con backoff exponencial (1s, 2s, 4s)
                    onRetry: (exception, timeSpan, retryCount, context) =>
                    {
                        Console.WriteLine($"Reintento {retryCount} después de {timeSpan.TotalSeconds:N1}s: " +
                                          $"{(exception.Result != null ? (int)exception.Result.StatusCode : exception.Exception.Message)}");
                    });
        }

        static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
        {
            return HttpPolicyExtensions
                .HandleTransientHttpError()
                .CircuitBreakerAsync(
                    handledEventsAllowedBeforeBreaking: 2, // Cuántos fallos antes de abrir el circuito
                    durationOfBreak: TimeSpan.FromSeconds(5), // Cuánto tiempo permanece abierto el circuito
                    onBreak: (exception, breakDelay) =>
                    {
                        Console.WriteLine($"Circuito Abierto! Fallo: {(exception.Result != null ? (int)exception.Result.StatusCode : exception.Exception.Message)}. " +
                                          $"Se mantendrá abierto por {breakDelay.TotalSeconds} segundos.");
                    },
                    onReset: () => Console.WriteLine("Circuito Cerrado! (Recuperado)"),
                    onHalfOpen: () => Console.WriteLine("Circuito Medio Abierto! (Probando si el servicio se recuperó)")
                );
        }

        static IAsyncPolicy<HttpResponseMessage> GetTimeoutPolicy()
        {
            return Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(5), TimeoutStrategy.Optimistic); // Lanza una excepción después de 5 segundos
        }
    }

    public class MyApiService
    {
        private readonly IHttpClientFactory _httpClientFactory;

        public MyApiService(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }

        public async Task GetDataWithRetries(string url)
        {
            Console.WriteLine($"Intentando obtener datos de {url} con reintentos...");
            try
            {
                var httpClient = _httpClientFactory.CreateClient("MyApiClient");
                var response = await httpClient.GetAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    Console.WriteLine($"✅ Éxito al obtener datos de {url}. Código: {(int)response.StatusCode}");
                }
                else
                {
                    Console.WriteLine($"❌ Fallo final al obtener datos de {url}. Código: {(int)response.StatusCode}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"⚠️ Excepción durante la operación con reintentos: {ex.Message}");
            }
        }

        public async Task GetDataWithCircuitBreaker(string url)
        {
            Console.WriteLine($"Intentando obtener datos de {url} con disyuntor...");
            try
            {
                var httpClient = _httpClientFactory.CreateClient("MyApiClient");
                var response = await httpClient.GetAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    Console.WriteLine($"✅ Éxito al obtener datos de {url}. Código: {(int)response.StatusCode}");
                }
                else
                {
                    Console.WriteLine($"❌ Fallo final al obtener datos de {url}. Código: {(int)response.StatusCode}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"⚠️ Excepción durante la operación con disyuntor: {ex.Message}");
            }
        }

        public async Task GetDataWithTimeout(string url)
        {
            Console.WriteLine($"Intentando obtener datos de {url} con timeout...");
            try
            {
                var httpClient = _httpClientFactory.CreateClient("MyApiClient");
                var response = await httpClient.GetAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    Console.WriteLine($"✅ Éxito al obtener datos de {url}. Código: {(int)response.StatusCode}");
                }
                else
                {
                    Console.WriteLine($"❌ Fallo final al obtener datos de {url}. Código: {(int)response.StatusCode}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"⚠️ Excepción durante la operación con timeout: {ex.Message}");
            }
        }
    }
}

Explicación del código de reintento:

  • Host.CreateDefaultBuilder(args).ConfigureServices(...): Configura el host de la aplicación y el contenedor de inyección de dependencias.
  • services.AddHttpClient("MyApiClient"): Registra un HttpClient nombrado.
  • .AddPolicyHandler(GetRetryPolicy()): Adjunta la política de reintento a este HttpClient.
  • HttpPolicyExtensions.HandleTransientHttpError(): Es un método de extensión de Polly que ya sabe qué códigos de estado HTTP (408, 5xx) y excepciones de red considerar como errores transitorios. Esto es muy útil.
  • OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound): Agregamos el código 404 (NotFound) para que también se reintente si nuestro mock de API lo devuelve (esto es solo para fines de demostración; en la realidad, un 404 podría no ser un error transitorio).
  • WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))): Configura 3 reintentos con un retraso exponencial (1s, 2s, 4s). retryAttempt es 1 para el primer reintento, 2 para el segundo, etc.
  • onRetry: Un callback que se ejecuta cada vez que se produce un reintento, útil para logging.
💡 Consejo: Es crucial que las políticas de reintento sean inteligentes. Reintentar indefinidamente puede sobrecargar un servicio ya estresado. Usa retroceso exponencial y un número máximo de reintentos.

⚡ Disyuntores (Circuit Breakers) con Polly

Mientras que los reintentos son excelentes para fallos transitorios que se resuelven rápidamente, ¿qué pasa si un servicio dependiente está completamente caído o muy lento? Reintentar una y otra vez solo empeoraría la situación, agotando recursos en nuestra aplicación y en el servicio fallido. Aquí es donde el Disyuntor entra en juego.

El patrón Circuit Breaker actúa como un protector. Si detecta que un servicio está fallando repetidamente, "rompe el circuito" (deja de enviar solicitudes a ese servicio) por un período de tiempo. Esto permite que el servicio dependiente se recupere y evita que nuestra aplicación malgaste recursos en intentos inútiles.

Estados del Disyuntor 🚦

Un disyuntor tiene tres estados principales:

  1. Cerrado (Closed): Las operaciones se ejecutan normalmente. Si los fallos exceden un umbral, el disyuntor pasa a Abierto.
  2. Abierto (Open): Las operaciones fallan instantáneamente sin intentar llamar al servicio dependiente. Después de un tiempo de espera configurado, el disyuntor pasa a Medio Abierto.
  3. Medio Abierto (Half-Open): Un número limitado de operaciones de prueba son permitidas. Si tienen éxito, el disyuntor vuelve a Cerrado. Si fallan, vuelve a Abierto.
CERRADO Peticiones permitidas Sistema saludable ABIERTO Peticiones bloqueadas Fallo detectado MEDIO ABIERTO Prueba de recuperación Flujo controlado Umbral de fallos Timeout Éxito total Nuevo fallo

Implementación del Disyuntor 🛡️

Retomando el Program.cs, ya hemos añadido la política de disyuntor. Analicemos la función GetCircuitBreakerPolicy():

static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(
            handledEventsAllowedBeforeBreaking: 2, // Cuántos fallos antes de abrir el circuito
            durationOfBreak: TimeSpan.FromSeconds(5), // Cuánto tiempo permanece abierto el circuito
            onBreak: (exception, breakDelay) =>
            {
                Console.WriteLine($"Circuito Abierto! Fallo: {(exception.Result != null ? (int)exception.Result.StatusCode : exception.Exception.Message)}. " +
                                  $"Se mantendrá abierto por {breakDelay.TotalSeconds} segundos.");
            },
            onReset: () => Console.WriteLine("Circuito Cerrado! (Recuperado)"),
            onHalfOpen: () => Console.WriteLine("Circuito Medio Abierto! (Probando si el servicio se recuperó)")
        );
}

Explicación:

  • handledEventsAllowedBeforeBreaking: 2: Si hay 2 fallos consecutivos (que HandleTransientHttpError captura), el circuito se abre.
  • durationOfBreak: TimeSpan.FromSeconds(5): El circuito permanecerá abierto durante 5 segundos. Durante este tiempo, cualquier llamada a este HttpClient fallará inmediatamente con un BrokenCircuitException sin intentar la operación real.
  • onBreak, onReset, onHalfOpen: Estos callbacks permiten registrar los cambios de estado del disyuntor, lo cual es muy útil para la monitorización y depuración.

Al ejecutar la demostración, verás cómo después de un par de fallos, las llamadas subsiguientes fallan instantáneamente hasta que el disyuntor se restablece.

⚠️ Advertencia: Un disyuntor bien configurado es clave. Si el `durationOfBreak` es demasiado corto, el servicio podría no recuperarse. Si es demasiado largo, tu aplicación permanecerá inoperable innecesariamente.

⏱️ Timeouts con Polly

Las operaciones de red pueden tardar más de lo esperado debido a lentitud del servidor, problemas de red o cuellos de botella. Un HttpClient sin un timeout explícito puede esperar indefinidamente, lo que lleva a:

  • Bloqueo de hilos: Agotamiento de los hilos del thread pool.
  • Mala experiencia de usuario: Aplicaciones que parecen "colgarse".
  • Acumulación de peticiones: En arquitecturas de microservicios, una solicitud lenta puede replicarse y sobrecargar otros servicios.

La política de timeout de Polly nos permite limitar cuánto tiempo estamos dispuestos a esperar por una respuesta.

Estrategias de Timeout ⏳

Polly ofrece dos estrategias de timeout:

  1. Optimistic: La operación se cancela tan pronto como el timeout expira. Utiliza CancellationToken. Es el enfoque recomendado para la mayoría de las operaciones asíncronas.
  2. Pessimistic: El timeout se controla alrededor de la ejecución real de la delegación. Esto significa que la operación subyacente puede seguir ejecutándose en segundo plano incluso después de que el timeout haya disparado, lo que puede ser un problema si la operación es costosa en recursos.

Siempre que sea posible, usa la estrategia optimistic.

Implementación del Timeout ⏱️

Nuestra función GetTimeoutPolicy() ya está implementada:

static IAsyncPolicy<HttpResponseMessage> GetTimeoutPolicy()
{
    return Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(5), TimeoutStrategy.Optimistic); // Lanza una excepción después de 5 segundos
}

Explicación:

  • Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(5), TimeoutStrategy.Optimistic): Configura un timeout de 5 segundos. Si la operación GetAsync no completa en 5 segundos, se lanzará una TimeoutRejectedException.

En la demostración, la llamada a https://httpstat.us/200?sleep=6000 (que espera 6 segundos) debería disparar el timeout, mientras que la que espera 1 segundo debería pasar sin problemas.

🔥 Importante: El timeout de Polly es adicional al `Timeout` del `HttpClient` en sí. Si configuras ambos, el que expire primero será el que actúe. Generalmente, es mejor gestionar los timeouts de forma centralizada con Polly.

🔗 Encadenamiento de Políticas

La verdadera potencia de Polly radica en la capacidad de encadenar múltiples políticas. Por ejemplo, querríamos reintentar una operación unas pocas veces, pero si esos reintentos exceden un límite de tiempo total, queremos que falle. O bien, si un servicio ya está en estado de disyuntor abierto, no tiene sentido intentar reintentar.

Polly resuelve esto permitiéndote definir un orden de ejecución. Las políticas más externas envuelven a las más internas.

Orden de las Políticas 🪜

Un orden común y recomendado para las políticas HTTP es:

  1. Timeout (externo): Falla rápidamente si la operación toma demasiado tiempo en general.
  2. Circuit Breaker: Protege el servicio externo de ser sobrecargado por fallos repetidos.
  3. Retry (interno): Maneja fallos transitorios permitiendo reintentos.

Consideremos el flujo de una solicitud:

  • Primero, el Timeout verifica el tiempo total. Si se excede, la solicitud se cancela.
  • Luego, el Circuit Breaker revisa su estado. Si está abierto, la solicitud falla instantáneamente. Si está cerrado o medio abierto, la solicitud continúa.
  • Finalmente, si la solicitud llega al servicio y falla, el Retry la reintenta un número limitado de veces.

En nuestro Program.cs, el encadenamiento se realiza así:

services.AddHttpClient("MyApiClient")
    .AddPolicyHandler(GetTimeoutPolicy())
    .AddPolicyHandler(GetCircuitBreakerPolicy())
    .AddPolicyHandler(GetRetryPolicy());

¡Espera un momento! Este orden es invertido respecto a cómo Polly los aplica. Cuando encadenas AddPolicyHandler, la última política añadida es la más interna, y la primera es la más externa.

Para el orden recomendado (Timeout -> Circuit Breaker -> Retry), la configuración correcta en AddHttpClient sería:

services.AddHttpClient("MyApiClient")
    .AddPolicyHandler(GetRetryPolicy())         // Más interno
    .AddPolicyHandler(GetCircuitBreakerPolicy()) // Intermedio
    .AddPolicyHandler(GetTimeoutPolicy());       // Más externo

Esto significa:

  1. La política de Timeout envuelve a todo. Si el tiempo total excede, cancela.
  2. Dentro del Timeout, la política de Circuit Breaker evalúa si el circuito está abierto.
  3. Si el circuito está cerrado/medio abierto, la política de Retry maneja los reintentos individuales.
📌 Nota: Polly siempre evalúa la política más externa primero y luego delega hacia adentro. Si una política externa decide que la operación debe fallar (ej. un timeout expira o un disyuntor está abierto), las políticas internas no se ejecutarán.

🧪 Ejecutando la Demostración

Ahora que tenemos el código, vamos a ejecutarlo para ver Polly en acción.

  1. Abre tu terminal en la carpeta PollyHttpClientDemo.
  2. Ejecuta: dotnet run

Observarás la salida en la consola:

  • Reintentos: Verás mensajes de reintentos para la URL que devuelve 500, hasta que se agoten los intentos o se obtenga una respuesta exitosa (si la URL cambiara o se simulara una recuperación). Para la URL con 200, debería ser un éxito directo.
  • Disyuntores: Después de los fallos iniciales, la tercera llamada debería fallar instantáneamente con un mensaje de "Circuito Abierto". Luego, después del tiempo de durationOfBreak, el disyuntor se pondrá en "Medio Abierto" y si la llamada de prueba tiene éxito, volverá a "Cerrado".
  • Timeouts: La URL con sleep=6000 debería mostrar una excepción de timeout, mientras que la URL con sleep=1000 debería completarse con éxito.
💡 Consejo: Juega con los valores de los parámetros (número de reintentos, duración del break, tiempo de timeout) para entender mejor cómo cada política afecta el comportamiento de tu aplicación.

📝 Consideraciones Adicionales y Buenas Prácticas

Logging y Monitorización 📊

Es vital integrar Polly con tu sistema de logging. Los callbacks onRetry, onBreak, onReset, onHalfOpen son puntos perfectos para registrar eventos importantes. Esto te ayudará a entender por qué tus llamadas están fallando y cómo tus políticas de resiliencia están actuando en producción.

Políticas Globales vs. Nombradas vs. Tipadas 🏷️

  • Políticas Nombradas: Es el enfoque que hemos usado, asociando políticas a un HttpClient específico por su nombre (ej. MyApiClient). Ideal para diferentes servicios con distintos requisitos de resiliencia.
  • Políticas Tipadas: Puedes crear typed clients que encapsulen la lógica del HttpClient y sus políticas, ofreciendo una API más limpia.
  • Políticas Globales: Menos común, pero posible si todas tus llamadas HTTP requieren las mismas políticas.

Otras Políticas de Polly ⚙️

Recuerda que Polly ofrece más que solo reintentos, disyuntores y timeouts:

  • Fallback: Proporciona un valor por defecto o ejecuta una acción alternativa cuando una operación falla.
  • Bulkhead: Limita el número de solicitudes concurrentes a un recurso específico, aislando fallos en un subsistema para que no afecten a toda la aplicación.
  • Cache: Almacena en caché los resultados de las operaciones exitosas para reducir la carga en los servicios externos y mejorar el rendimiento.
Ejemplo de Política de Fallback
static IAsyncPolicy<HttpResponseMessage> GetFallbackPolicy()
{
    return Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
                 .Or<HttpRequestException>()
                 .FallbackAsync(async cancellationToken =>
                 {
                     Console.WriteLine("Fallback activado: Devolviendo respuesta por defecto.");
                     var defaultResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
                     {
                         Content = new StringContent("{\"message\":\"Datos por defecto\"}")
                     };
                     return await Task.FromResult(defaultResponse);
                 });
}

// Para usarla:
// services.AddHttpClient("MyApiClient")
//     .AddPolicyHandler(GetRetryPolicy())
//     .AddPolicyHandler(GetCircuitBreakerPolicy())
//     .AddPolicyHandler(GetTimeoutPolicy())
//     .AddPolicyHandler(GetFallbackPolicy()); // Añadir al final del encadenamiento

La política de Fallback debería ser la más externa para envolver los posibles fallos de todas las políticas anteriores y proporcionar una respuesta alternativa final.

Pruebas de Resiliencia 🔬

No basta con implementar las políticas; debes probarlas. Herramientas como httpstat.us (usada en este tutorial) son excelentes para simular diferentes códigos de estado HTTP y latencias. También puedes usar herramientas de inyección de fallos en tus entornos de prueba.

Buenas Prácticas

  • No reintentar POST/PUT/DELETE idempotentes: Ten cuidado al reintentar operaciones que no sean idempotentes (que producen el mismo resultado si se ejecutan varias veces), a menos que tengas un mecanismo para gestionarlo. Las operaciones GET son generalmente seguras para reintentar.
  • Configuración de Polly: Almacena la configuración de tus políticas (número de reintentos, tiempos, etc.) en archivos de configuración (ej. appsettings.json) para que puedan ajustarse sin recompilar el código.
  • Contexto de Polly: Usa el objeto Context de Polly para pasar información relevante (como un ID de correlación) a través de las políticas y los callbacks.

Conclusión ✅

Dominar la resiliencia es un paso fundamental para construir aplicaciones C# y .NET de alta calidad que puedan soportar las realidades de los sistemas distribuidos. Polly, en combinación con HttpClient y IHttpClientFactory, ofrece un marco potente y flexible para implementar patrones de resiliencia como reintentos, disyuntores y timeouts.

Al aplicar los conocimientos de este tutorial, estarás en una excelente posición para diseñar y desarrollar sistemas que no solo funcionen, sino que también sean robustos, tolerantes a fallos y mantengan una experiencia de usuario consistente incluso cuando las cosas no salgan según lo planeado.

¡Experimenta, prueba y adapta estas políticas a las necesidades específicas de tus servicios para construir APIs verdaderamente resilientes!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!