Nuevo CURSO COMPLETO para monetizar tus aplicaciones con Stripe completamente gratuito y disponible en la web! (También disponible en YouTube)

La idempotencia en el Desarrollo de Software

Este post viene a raíz de un bug que se detectó en el software de un cliente.

 

Para resumir la situación, en la base de datos había dos registros de un elemento, cuando en teoría solo puede haber uno. Después de investigar y ver que el bug no era en mi aplicación, pase a comunicarlo y me dijeron que no pueden hacer nada ya que el evento se ha procesado dos veces.

 

Yo, al no ser mi empresa, tampoco me puse a discutir, aunque si que les mandé un enlace a la página de idempotencia en wikipedia. 

 

 

 

1 - Qué es idempotencia?

 

La idempotencia cuando hablamos de software es una técnica que nos permite realizar una operación múltiples veces, obteniendo siempre el mismo resultado

Por ejemplo, si procesamos un evento dos veces, la segunda vez no debería alterar el resultado. En programación especificamos una propiedad en un evento o en una request para evitar que se pueda ejecutar más de una única vez. 

Para este ejemplo vamos a verlo dentro del proyecto de Distribt, ya que la idempotencia es muy importante en los sistemas distribuidos, pero mas adelante también pondremos un ejemplo de idempotencia en REST.

 

Un caso muy común de ejemplo es que si tenemos un producto con un inventario de 10 y creamos un pedido que trae esos dos productos, debemos generar un evento para reducir el inventario. Esta acción sigue la misma lógica que vimos en el post sobre event sourcing.

event sourcing

 

Hasta aquí todo bien, pero, qué pasa si el sistema tiene un fallo y el evento se procesa dos veces? 

Pues que en la situación actual procesaremos dos veces y pasaremos a tener 6 productos en vez de 8, con lo que la cuenta del inventario sería errónea, ya que el evento se ha procesado dos veces, pero solo una orden de compra ha sido creada.

 

Aquí es donde entra la idempotencia.

 

 

2 - Implementar idempotencia en un Sistema

 

Lo primero que tenemos que hacer es incluir una propiedad a todos nuestros eventos que haga contenga este id único, es muy común utilizar “IdempotentId” o “messageidentifier” como nombre de la propiedad, obviamente le puedes poner el nombre que quieras.

 

Para este ejemplo estoy utilizando mi librería de Distribt, y esa propiedad está en la interfaz IMessage, lo que implica que tanto los mensajes de integración como los de dominio van a contenerla. 

public interface IMessage
{
    /// <summary>
    /// Must be unique;
    /// </summary>
    public string MessageIdentifier { get; }
    /// <summary>
    /// Name for the message, useful in logs/databases, etc
    /// </summary>
    public string Name { get; }
}

Y a la hora de crear los mensajes, se le asigna una GUID de forma automática.

Personalmente automatizar con una propiedad que sabes que no se va a repetir es mi opción favorita, técnicamente puedes utilizar el ID o una combinación pero cualquier cosa que definas “manualmente” corre el riesgo de repetirse, pero, si puedes garantizar que es una clave única, puedes usar lo que quieras.

private static IntegrationMessage<T> ToTypedIntegrationEvent<T>(T message, Metadata metadata)
{
    //El primer parámetro es el identificador del mensaje 👇
    return new IntegrationMessage<T>(Guid.NewGuid().ToString(), typeof(T).Name, message, metadata);
}

Ahora todos los eventos tienen la propiedad asignada con un valor distinto. 

Ojo, la idempotencia no prevé que si tienes un bug y generas dos eventos simultáneamente uno no se procese, ya que técnicamente son dos eventos distintos, lo que prevé es que el mismo evento se ejecute múltiples veces. 

 

Finalmente lo que debes hacer en el cosumidor es comprobar que ese evento no esta procesado y sis lo esta, actuar acordemente.

 

La idempotencia es muy popular dentro de los sistemas distribuidos, pero yo personalmente también recomiendo utilizarla en las APIs en ciertos escenarios. Para evitar que la misma llamada HTTP se ejecute dos veces.

En caso de utilizar una llamada HTTP normalmente mandaremos el IdempotentId como un header, aunque cada sistema es diferente.

 

 

3 - Cómo afrontar la creación de un sistema idempotente

 

A la hora de construir un sistema, debemos tener varias cosas en mente.

 

Debemos tener en cuenta de si es un sistema, síncrono (llamadas HTTP) o un sistema asíncrono (sistema distribuido) porque depende del sistema podemos hacer una serie de acciones o no. 

 

Lo principal es saber cuando una llamada o evento ya ha pasado por el sistema. Para ello hay varias formas de hacerlo, de hecho yo he visto de todo.

Para ello lo que podemos crear es una tabla donde la clave primaria es el IdempotentId una columna donde contiene todo el evento. A Partir de aquí todo es opcional, una segunda columna contiene la fecha y la hora  una cuarta con el estado de la operación y una última con el resultado del proceso.

Este tipo de tabla puede estar tanto en una caché como Redis, en una base de datos, o incluso en memoria aunque quizá no sea la mejor opción en producción.

 

Si estuvieras en un sistema distribuído podrías simplemente almacenar el ID, ya que no vas a devolver ninguna información.

El campo de estado también es opcional y mantenerlo requiere trabajo. 

 

Ahora viene la explicación del campo de la fecha y la hora, hay quien te dirá que almacenar toda la información está bien y otros que está mal. La realidad es que a esto se le llama TTL (time to live) o tiempo de vida, y muchas empresas y sistemas almacenan 24 horas, eso quiere decir que si el evento llega en 48 horas este se volverá a ejecutar. 

La decisión de si almacenar indefinidamente los IDs e incluso los eventos radica en, no solo el dominio, sino en la parte económica, ya que en sistemas distribuidos con muchos mensajes puede ser muy caro. 

 

Mi recomendación es almacenarlos durante 24 horas o incluso una semana, pero no más de eso. 

 

 

Y ahora llega la principal diferencia entre un sistema síncrono y uno asíncrono. 

Si tenemos un sistema asíncrono, donde nadie está esperando una respuesta, podemos simplemente terminar el proceso ahí, comprobar que el ID está en la base de datos y si lo está, terminar el proceso.

 

 

3.1 - Idempotencia en sistemas síncronos (REST)

 

En el caso de un sistema síncrono la cosa cambia debido a que quien hace la llamada espera una respuesta inmediata.

 

Llegados a este punto tenemos dos opciones.

La primera es fallar la segunda llamada (y todas las subsecuentes):

diagrama secuencia idempotencia

A primera vista, esta acción puede parecer la que más sentido tiene pues obviamente la segunda llamada es errónea y no debería suceder.

 

Pero en el mundo real, esa segunda llamada está sucediendo porque quién está mandando la información no tiene ni idea de donde está la primera, ya sea por un bug o por un fallo de red, etc. Por lo tanto, en este caso, lo más recomendable es responder a la segunda llamada con el resultado de la primera

 

Esto quiere decir que si, debemos almacenar el resultado de forma temporal. Pero al cliente le estamos haciendo un gran favor ya que el sistema es mucho más simple. Y facil de administrar, si no devolvemos el resultado, tendremos que crear otro sistema para que busquen lo que sea que estaban buscando.

En el supuesto de una orden de compra, simpremente devolvemos el ID.

 

Y así nos aseguramos de ser más consistentes  en el funcionamiento de la API.

 

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café