En el desarrollo de software moderno, los fallos transitorios —como una pérdida momentánea de conectividad, un timeout de red o un servicio externo saturado— son inevitables. El Retry Pattern (Patrón de Reintento) permite que una aplicación gestione estas interrupciones de forma transparente, reintentando una operación fallida un número determinado de veces antes de desistir. En este artículo, analizamos cómo implementar este patrón en .NET 10 utilizando las últimas mejoras en las librerías de resiliencia de Microsoft.
1. ¿Qué es el Retry Pattern?
El patrón de reintento es una estrategia de estabilidad que permite a un sistema recuperarse automáticamente de errores temporales. Su funcionamiento es sencillo: si una operación falla, el sistema espera un tiempo determinado y vuelve a intentarlo.
Existen tres estrategias comunes:
- Inmediato: El reintento se realiza al instante.
- Intervalo Fijo: Se espera la misma cantidad de tiempo entre intentos.
- Exponencial (Exponential Backoff): El tiempo de espera aumenta exponencialmente (ej. 2s, 4s, 8s…) para evitar saturar un servicio que ya está bajo estrés.
2. Configuración en .NET 10 con Microsoft.Extensions.Resilience
A partir de las versiones más recientes de .NET, Microsoft ha integrado profundamente las estrategias de resiliencia. Ya no es estrictamente necesario configurar Polly de forma aislada, sino que podemos usar el paquete estándar de resiliencia.
Instalación de dependencias
Bash
dotnet add package Microsoft.Extensions.Http.Resilience
3. Implementación Práctica: Cliente HTTP con Reintentos
Supongamos que nuestra aplicación consume una API de facturación que presenta micro-cortes ocasionales. Configuraremos un pipeline de reintento con espera exponencial y jitter (una variación aleatoria para evitar que múltiples instancias reintenten exactamente al mismo tiempo).
C#
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http.Resilience;
using Polly;
var services = new ServiceCollection();
services.AddHttpClient("FacturacionAPI", client =>
{
client.BaseAddress = new Uri("https://api.ejemplo.com/");
})
.AddStandardResilienceHandler(options =>
{
// Configuración del Retry dentro del pipeline estándar
options.Retry.MaxRetryAttempts = 3;
options.Retry.BackoffType = DelayBackoffType.Exponential;
options.Retry.UseJitter = true;
options.Retry.Delay = TimeSpan.FromSeconds(2);
});
4. Uso de Resilience Pipelines para Lógica de Negocio
Si necesitamos aplicar reintentos a una lógica que no sea estrictamente HTTP (por ejemplo, una consulta a base de datos o una escritura en disco), podemos definir un pipeline genérico:
C#
var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<TimeoutException>(),
MaxRetryAttempts = 4,
Delay = TimeSpan.FromSeconds(1),
BackoffType = DelayBackoffType.Constant,
OnRetry = args =>
{
Console.WriteLine($"Intento {args.AttemptNumber} fallido. Reintentando...");
return default;
}
})
.Build();
// Ejecución de la tarea protegida
await pipeline.ExecuteAsync(async token =>
{
await MiMetodoCriticoAsync(token);
});
5. Consideraciones de Diseño y Buenas Prácticas
Aunque el patrón de reintento es potente, debe usarse con cautela:
- Idempotencia: Asegúrate de que la operación sea segura de reintentar. Si una operación de «crear pago» falla a mitad de camino, reintentarla sin control podría duplicar el cargo.
- Límite de Intentos: Nunca utilices reintentos infinitos; esto podría bloquear hilos de ejecución y agotar los recursos del sistema.
- Combinación con Circuit Breaker: Para una resiliencia completa, el Retry Pattern suele trabajar junto al Circuit Breaker. Mientras el Retry intenta solucionar fallos momentáneos, el Circuit Breaker detiene las peticiones si el fallo persiste en el tiempo.
Conclusión
La implementación del Retry Pattern en .NET 10 es más sencilla y legible que nunca. Al adoptar estas estrategias, transformamos aplicaciones frágiles en sistemas robustos capaces de soportar la inestabilidad inherente a los entornos de nube y microservicios, mejorando significativamente la experiencia del usuario final.