Que es un patrón en desarrollo de software
Antes de ver el patrón retry, vamos a empezar por el principio, vamos a ver que es un patrón. En el desarrollo de software, es de los más normal encontrarnos con problemas y/o retos que son recurrentes y que siempre están ahí. Retos que son agnósticos al lenguaje de programación que utilices, incluso si estas haciendo algo con alguna herramienta low code, te encontraras con estas casuísticas. ¿Cómo accedemos a la base de datos? ¿Cómo tratamos los errores? ¿Cómo muevo millones de registros en el menor tiempo posible?, etc. ¿no te ha pasado nunca que tienes un error, un problema, algo que no sabes como hacer, lo buscas en StackOverflow, y hay cientos de respuestas? Seguro que creías que eso solo te había pasado a ti, pero no, en software, todos nos encontramos con las mismas problemáticas.
Un patrón es un diseño que nos permite «atacar» a una casuística concreta, no es mas que una forma concreta de resolver un problema. Puedes profundizar mucho más en la definición desde la página de Wikipedia.
Los patrones cloud, por lo tanto, serán soluciones a problemas concretos que se dan en los entornos y desarrollos pensados para la nube. Recuerda, son soluciones agnosticas a la tecnologia que uses. Yo para mis ejemplos voy a usar Azure y C#, pero podrías aplicar el patrón a AWS y Java, por ejemplo, o picar el código en Python, es indiferente.
Que es el patrón retry
El patrón retry se usa para tratar posibles errores temporales de comunicación entre distintos sistemas como por ejemplo en sistemas distribuidos, arquitecturas de microservicios o la utilización de APIs.
En en caso de encontrarnos con un micro-corte en la red o que un servicio este temporalmente no accesible cuando intentamos acceder a el, si implementamos una estrategia con patrón retry, en vez de fallar, el sistema lo volverá a intentar pasado un tiempo especificado.
En el siguiente esquema se muestra el comportamiento esperado: Se llama al servicio, en caso de error comprueba si se ha llegado al numero de reintentos máximos (no queremos tampoco quedarnos en bucle intentándolo, si el fallo persiste tendríamos problemas), en caso de que no, volvemos a llamar al servicio, si no, devolvemos un error.
Cuando hay que usarlo y cuando no
Usaremos retry cuando el error que se pueda producir sea transitorio, de corta duración y se pueda solucionar con un reintento.
No usaremos este patrón para tratar errores de larga duración, ni para tratar excepciones internas ni como alternativas a la escalabilidad (voy intentando hasta que el sistema responda).
Ejemplo
Todo lo comentado anteriormente esta muy bien, pero seguro que ya estaras en modo «show me the code». Vamos a ver un ejemplo. Todo el código que comento, lo puedes encontrar en el siguiente repositorio de GIT: https://github.com/DaniCCardenas/RetryPattern
En el siguiente ejemplo, vamos a crear un servicio que llama a una API para obtener un cocktel random. El servicio es lo de menos, simplemente lo vamos a usar como ejemplo. Para poder simular el comportamiento, he añadido un componente random para que falle:
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
namespace RetryPattern
{
public class CocktailService
{
public string GetRandomCocktail()
{
//Se simula un error
var seed = Environment.TickCount;
var random = new Random(seed);
var value = random.Next(0, 5);
if(value < 2)
{
throw new Exception("Error en el servicio");
}
HttpClient client = new HttpClient();
var response = client.GetAsync("https://www.thecocktaildb.com/api/json/v1/1/random.php").Result;
return response.Content.ReadAsStringAsync().Result;
}
}
}
Ahora que tenemos el servicio listo para ser usado, vamos a crear el retry. Para ello creamos un mentodo, donde dada una función intente ejecutarla n veces hasta que funcione. Para cada intento vamos a dejar pasar algo de tiempo, que iremos incrementando exponencialmente. Si llegamos al máximo de reintentos no ha habido un resultado exitoso, devolvemos una excepción.
using System;
using System.Threading;
namespace RetryPattern
{
public class Retry
{
public static TResult Do<TResult>(Func<TResult> func, int maxRetries = 3)
{
int retryCount = 0;
while (true)
{
try
{
return func();
}
catch (Exception)
{
TimeSpan delay;
if (++retryCount >= maxRetries)
{
throw;
}
//exponential delay
delay = TimeSpan.FromSeconds(Math.Pow(2, retryCount));
Thread.Sleep(delay);
}
}
}
}
}
Y ahora, ¿Cómo lo usamos?. Muy sencillo: no llamaremos directamente al servicio, si no que lo llamaremos a través de nuestra implementación de retry:
using System;
namespace RetryPattern
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Se inicia el ejemplo");
var service = new CocktailService();
var res = Retry.Do(() => service.GetRandomCocktail());
}
}
}
Ya lo tendríamos. Es una implementación sencilla que nos sirve perfectamente para el ejemplo, existen librerías para gestionar todo esto como Polly, pero he preferido para el ejemplo crear mi propia implementación a usar la librería.
Enlaces de interés
Guía de reintentos para servicios Azure
https://docs.microsoft.com/es-es/azure/architecture/patterns/retry