En la mayoría de arquitecturas distribuidas, el reto no es mantener un orden global de mensajes, sino asegurar que los eventos relacionados a una misma entidad —como un OrderId o CustomerId— sean procesados secuencialmente. Este principio es clave para preservar la consistencia y evitar errores sutiles en flujos de negocio complejos.
El atractivo inicial de los eventos de dominio
Los eventos de dominio ofrecen una solución elegante: cada vez que un agregado cambia de estado, emite un evento que desencadena acciones en otros componentes. Por ejemplo, un pedido puede generar eventos como OrderPlaced, PaymentCaptured y OrderShipped. Sin embargo, este mecanismo presenta fragilidades cuando se utiliza para integraciones, ya que pueden surgir problemas de duplicidad, reintentos y desorden si la publicación de eventos falla o se produce fuera de la transacción.
Patrón Outbox: fiabilidad, pero no orden
El patrón Outbox refuerza la fiabilidad al almacenar los eventos pendientes en la misma transacción que la actualización del agregado. Un proceso aparte se encarga de publicar estos eventos a la cola de mensajes, asegurando que nada se pierda ni se publique duplicado. No obstante, aunque mejora la robustez, no garantiza el orden de procesamiento una vez que los mensajes entran en la cola.
El problema de los consumidores concurrentes
Para escalar, es habitual aumentar el número de consumidores de una cola (competing consumers). Pero esta técnica puede romper el orden esperado: dos consumidores distintos podrían procesar eventos del mismo agregado en paralelo y fuera de secuencia. Esto genera inconsistencias, especialmente bajo carga o en presencia de reintentos automáticos.
La verdadera necesidad: orden por agregado
Lo ideal es mantener hilos de procesamiento independientes y ordenados por cada agregado. Los agregados ya delimitan las fronteras de consistencia y los eventos suelen generarse secuencialmente. Si se asegurara que sólo un consumidor procese eventos por cada clave agregada, el problema prácticamente desaparece.
¿Una solución simple? Un único consumidor
Implementar un solo consumidor garantiza el orden, pero limita la escalabilidad y la capacidad de procesamiento. En sistemas de alto volumen, esto puede ser un cuello de botella.
Procesamiento encadenado: el paso hacia las sagas
Una estrategia más sofisticada es que, tras procesar un evento de un agregado, el sistema publique explícitamente el siguiente evento a procesar. Así, se asegura que sólo un mensaje por agregado está activo, manteniendo el orden y permitiendo escalar horizontalmente en diferentes agregados. Este patrón lleva naturalmente a la saga coreografiada: un flujo de trabajo donde cada etapa desencadena la siguiente mediante eventos.
Saga coreografiada vs. saga orquestada
La saga coreografiada distribuye el control entre los participantes, pero puede dificultar la trazabilidad y la gestión de errores. Cuando el control, la visibilidad y la gestión de excepciones se vuelven imprescindibles, conviene evolucionar hacia una saga orquestada o saga con máquina de estados. Aquí, un componente central mantiene el estado del flujo, decide los pasos siguientes y facilita la observabilidad del proceso.
Soporte de los brokers de mensajes
Algunos brokers como Azure Service Bus (con sesiones), Amazon SQS FIFO (con message groups), Kafka (particiones) o RabbitMQ (single active consumer) ofrecen mecanismos para mantener el orden por clave. Estos ayudan a evitar el procesamiento concurrente por agregado, pero no reemplazan la necesidad de patrones como Outbox, consumidores idempotentes o la modelización explícita de flujos de trabajo cuando los procesos de negocio lo exigen.
Conclusión
La búsqueda del orden de mensajes por agregado en sistemas distribuidos termina, casi inevitablemente, en la implementación de sagas. Al comprender esto, dejamos de luchar contra las colas y diseñamos flujos que realmente responden a las necesidades del negocio, combinando fiabilidad, orden y escalabilidad.
Fuente: Artículo reescrito y adaptado de milanjovanovic.tech.




