¿Transacciones perdidas? Cómo diseñar arquitecturas Event-Driven
Aprende a garantizar la consistencia eventual y gestionar mensajes duplicados en sistemas distribuidos complejos usando el patrón Sagas.
Resumen
- Los sistemas distribuidos cambian transacciones atómicas por consistencia eventual para ganar escala y resiliencia operativa.
- El patrón Sagas divide flujos complejos en pasos locales que pueden revertirse si ocurre un fallo a mitad del proceso.
- Los mensajes duplicados exigen idempotencia estricta para evitar que operaciones financieras o de registro se ejecuten dos veces.
- Las arquitecturas orientadas a eventos exigen observabilidad continua para rastrear cuellos de botella y evitar fallos silenciosos.
- Diseñar flujos resilientes protege el negocio contra pérdidas económicas causadas por caídas repentinas de red o servidores.
¿Por qué las transacciones tradicionales fallan en la nube?
Cuando construimos sistemas monolíticos, confiar en bases de datos relacionales y transacciones ACID parece la solución definitiva. En la práctica, cuando dividimos este sistema en microservicios repartidos por diferentes servidores, el modelo tradicional colapsa. En la nube, la red falla, las instancias caen y la famosa consistencia inmediata desaparece, dando paso a la consistencia eventual, donde los datos tardan unos segundos en alinearse entre los servicios.
Las arquitecturas orientadas a eventos resuelven el problema de escala al permitir que diferentes servicios se comuniquen de forma asíncrona, enviando avisos de que algo ha sucedido. Sin embargo, esta libertad trae un efecto secundario peligroso: el riesgo real de perder transacciones a mitad de camino o procesar el mismo mensaje dos veces. Para evitar el caos, los ingenieros deben abandonar la ilusión de que el mundo distribuido funciona igual que un único servidor local.
El reto de la consistencia eventual en microservicios
Imagina que un cliente realiza una compra en un e-commerce. El servicio de pago procesa la tarjeta, pero el servicio de inventario se cae justo después antes de apartar el producto. En una base de datos tradicional, todo se cancelaría con un clic. En un sistema distribuido, el dinero ya salió de la cuenta, pero el inventario sigue intacto, generando una discrepancia financiera grave.
La consistencia eventual significa que los datos no están sincronizados todo el tiempo, pero se alinearán en breve. Para que esto funcione sin dolores de cabeza, la ingeniería de software recurre a patrones de diseño que aceptan el riesgo del fallo temporal y crean mecanismos automáticos de recuperación. Aquí es donde entran estrategias como el patrón Sagas para coordinar etapas sin bloquear la base de datos principal.
Cómo funciona el patrón Sagas en la práctica
El patrón Sagas reemplaza la transacción gigante por una secuencia de transacciones locales en cada microservicio involucrado. Cada paso actualiza su propia base de datos y emite un nuevo evento para el siguiente servicio en la cola. Si todo sale bien, el flujo termina con éxito. ¿Pero qué pasa si el tercer servicio falla? En lugar de un 'rollback' global que no existe en la nube, el sistema ejecuta transacciones compensatorias para deshacer lo hecho paso a paso.
Si el inventario falla tras aprobarse el pago, la Saga activa automáticamente una rutina para reembolsar al cliente. En la práctica, esto exige que cada operación de escritura tenga un inverso lógico bien diseñado. No es un proceso instantáneo, pero garantiza que todo el sistema vuelva a un estado coherente sin bloquear la operación de otros clientes que siguen comprando en la web.
El peligro invisible de los mensajes duplicados
Otro fantasma en las arquitecturas orientadas a eventos es la entrega duplicada de mensajes. Debido a inestabilidades en la red, un broker como Kafka o RabbitMQ puede entregar el mismo evento de cobro dos veces al mismo microservicio. Si el microservicio no está preparado, al cliente se le cobrará el doble por el mismo pedido.
La defensa contra este problema se llama idempotencia — una propiedad matemática que garantiza que ejecutar la misma operación diez veces produce exactamente el mismo resultado que ejecutarla una sola vez. En la práctica, esto se implementa registrando un identificador único de cada transacción en una tabla de control. Si el mensaje llega de nuevo con el mismo ID, el sistema simplemente descarta el duplicado y devuelve el éxito anterior.
Garantizando entregas fiables con el patrón Outbox
Para evitar que un evento se pierda entre el momento en que se actualiza la base de datos y el momento en que se envía el mensaje a la cola, utilizamos el Transactional Outbox Pattern. En lugar de publicar el evento directamente en la red, la aplicación escribe el dato y el evento en una misma tabla transacional local. Un proceso secundario lee esta tabla y despacha los mensajes con seguridad hacia el bus de eventos.
Esta estrategia garantiza que ningún evento quede atrás, incluso si el servicio cae justo en el momento del disparo. Al integrar esta práctica con una arquitectura de lectura basada en CQRS, logramos separar claramente quién escribe los datos de quién solo consulta, mejorando drásticamente el rendimiento general del sistema. Para profundizar en estos conceptos arquitectónicos en producción, consulte también nuestro análisis técnico sobre IA alucinando? Cómo crear guardrails en LlamaIndex.
Consideraciones finales sobre resiliencia distribuida
Diseñar arquitecturas orientadas a eventos exige un cambio profundo de mentalidad: dejar de intentar evitar todos los fallos y pasar a diseñar sistemas que sobrevivan elegantemente a ellos. Combinando consistencia eventual, transacciones compensatorias mediante Sagas y comprobaciones estrictas de idempotencia, tu ingeniería gana la robustez necesaria para escalar sin perder datos en el camino.