Polly es una librería Open Source para .Net que permite implementar ciertos patrones de resiliencia y gestión/recuperación de excepciones en aplicaciones .Net.
Cuando nos enfrentamos con procesos que son susceptibles de emitir excepciones existen varios patrones o estrategias que podemos seguir para gestionar el comportamiento ante el error. Sin embargo, implementar estos patrones requiere tiempo y código.
Polly simplifica el proceso permitiéndonos implementar los patrones como Retry, Timeout o Circuit-breaker de forma robusta y de forma muy sencilla a través de su sintaxis Fluent.
Los patrones que podemos implementar con Polly son:
- Retry: Intenta ejecutar el proceso N veces o de forma indeterminada.
- Circuit-breaker: Cuando ocurren varios fallos, inhabilita el proceso durante un tiempo.
- Timeout: Intenta ejecutar un proceso durante un cierto tiempo.
- Fallback: Ejecuta un proceso ante un error (similar a try-catch).
- Bulkhead: Limita la cantidad de procesos que pueden ejecutarse en paralelo.
- Cache: Evita ejecutar un proceso si ya se ha ejecutado anteriormente.
- PolicyWrap: Combina varias reglas anteriores.
Cada una de estos patrones tienen multitud de parámetros y opciones para adaptarlos a nuestras necesidades. Para más información sobre ellos, consultar la abundante documentación disponible en la página web del proyectohttps://github.com/App-vNext/Polly . Polly es una librería pensada principalmente para procesos Web. Así veréis que muchos ejemplos disponibles se centran en temas relativos a peticiones Http. Sin embargo, Polly puede ser útil en una gran cantidad de situaciones y proyectos aunque no estén en absoluto relacionados con la comunicación.
Para ilustrarlo vamos a ver unos pequeños ejemplos sobre el uso y funcionamiento de la librería. Para ello, simplemente creamos una aplicación de consola y añadimos Polly a través del gestor de paquetes Nuget.
Patrón retry
El patrón Retry simplemente intenta repetir la acción en caso de que se produzca una excepción.
using Polly;
sing Polly.Timeout;
using System;
using System.Diagnostics;
namespace TestPolly
{
class Program
{
static void Main(string[] args)
{
testRetry();
}
static void testRetry()
{
try
{
Policy
.Handle<Exception>()
.Retry(3)
.Execute(RetrySampleMethod);
}
catch (Exception)
{
Console.WriteLine("Retry exceed");
}
Console.WriteLine("Finish");
Console.ReadLine();
}
static int counter = 0;
[DebuggerNonUserCode]
static void RetrySampleMethod()
{
Console.WriteLine(counter);
counter++;
throw new Exception();
}
}
}
Como vemos, tenemos la función ‘testRetry()’, que ejecuta la función ‘RetrySampleMethod()‘. En esta función, que simularía nuestro código real, hemos añadido una excepción. En general, usaríamos un tipo específico de excepción, pero para este ejemplo simplemente vamos a emplear la clase base.
Ahora, en testRetry, hemos empleado Polly para ejecutar el código 4 veces (1 original + 3 reintentos). Al acumular los 4 errores, lanzará una Excepción, que hemos capturado en un bloque Try Catch
Patrón Fallback
Para evitar este bloque Try Catch y hacerlo más en la “forma Polly”, podemos sustituirlo por una combinación con Fallback. Por tanto, el código anterior es equivalente a,
using Polly;
using Polly.Timeout;
using System;
using System.Diagnostics;
namespace TestPolly
{
class Program
{
static void Main(string[] args)
{
testRetry();
}
static void testRetry()
{
Policy
.Handle<Exception>()
.Fallback(_ => Console.WriteLine("Retry exceed"))
.Wrap(
Policy
.Handle<Exception>()
.Retry(3)
)
.Execute(RetrySampleMethod);
Console.WriteLine("Finish");
Console.ReadLine();
}
static int counter = 0;
[DebuggerNonUserCode]
static void RetrySampleMethod()
{
Console.WriteLine(counter);
counter++;
throw new Exception();
}
}
}
Patrón Timeout
EL patrón Timeout consiste en detener una acción si el tiempo de ejecución es mayor que un intervalo determinado.
using Polly;
using Polly.Timeout;
using System;
using System.Diagnostics;
namespace TestLibraries
{
class Program
{
static void Main(string[] args)
{
testTimeout();
}
static void testTimeout()
{
Policy.Timeout(1, TimeoutStrategy.Pessimistic, onTimeout: (context, timespan, task) =>
{
Console.WriteLine("Timeout");
})
.ExecuteAndCapture(TimeoutSampleMethod);
Console.WriteLine("Finish");
Console.ReadLine();
}
[DebuggerNonUserCode]
static void TimeoutSampleMethod()
{
System.Threading.Thread.Sleep(2000);
}
}
}
En este caso nuestro código está simulado por la función ‘TimeoutSampleMethod()’ que, como vemos, únicamente tiene una espera bloqueante de 2 segundos. En la función ‘testTimeout()’ empleamos Polly para lanzar la función anterior con un Timeout de 1 segundo. La estrategia pesimista se emplea para funciones que no implementan CancelationToken.
Patrón Circuit-Breaker
El patrón Circuit-Breaker detiene las peticiones a un sistema si detecta una serie de errores cercanos en el tiempo, y los desactiva durante un intervalo.
using Polly;
using Polly.Timeout;
using System;
using System.Diagnostics;
namespace TestLibraries
{
class Program
{
static void Main(string[] args)
{
testCircuitBreak();
}
private static void testCircuitBreak()
{
var pol = Policy
.Handle<Exception>()
.CircuitBreaker(
exceptionsAllowedBeforeBreaking: 10,
onBreak: (exception, timespan) => { Console.WriteLine("Break"); },
onReset: () => { Console.WriteLine("Reset"); },
durationOfBreak: TimeSpan.FromSeconds(5)
);
while(true)
{
pol.ExecuteAndCapture(CircuitBreakSampleMethod);
}
}
[DebuggerNonUserCode]
static void CircuitBreakSampleMethod()
{
System.Threading.Thread.Sleep(100);
Console.WriteLine("Error");
throw new Exception();
}
}
}
Por último, veréis que todos los ejemplos hemos marcado la función que emite la Excepcion con el atributo [DebuggerNonUserCode]. Esto es necesario para debuggear en Visual Studio, porque de lo contrario captura la excepción que lanzamos, incluso aunque esté gestionada por Polly. En caso de ser código de una librería externa a nuestro código, este atributo no sería necesario.
Así de sencillo es implementar patrones con la librería Polly. Una herramienta muy útil, que debería formar parte de vuestra colección de librerías favoritas.