Type something to search...
Estándar de Arquitectura: Transacciones Distribuidas (Patrón Saga)

Estándar de Arquitectura: Transacciones Distribuidas (Patrón Saga)

PARTE I: PRINCIPIOS Y NORMATIVA

1. Fundamentos de Consistencia Eventual

Debido a la naturaleza distribuida del sistema, se abandona el modelo ACID tradicional (Atomicidad inmediata con bloqueos) en favor del modelo BASE (Basically Available, Soft state, Eventually consistent).

Implicaciones arquitectónicas:

  • Los datos pueden estar temporalmente inconsistentes entre servicios
  • La consistencia se alcanza mediante propagación de eventos y compensaciones
  • Cada servicio mantiene su propia fuente de verdad (base de datos)
  • No existen transacciones atómicas que abarquen múltiples servicios

Garantías del sistema:

  • Disponibilidad: Los servicios responden incluso si otros están caídos
  • Durabilidad: Los eventos se persisten antes de considerarse publicados
  • Convergencia: El sistema eventualmente alcanzará un estado consistente

2. Patrones Permitidos

2.1 Patrón Primario: Saga basada en Coreografía

Los servicios participantes reaccionan a eventos de dominio de manera autónoma sin conocer el flujo completo de la transacción distribuida. Cada servicio:

  • Escucha eventos relevantes de su dominio
  • Ejecuta su lógica de negocio local
  • Publica eventos de resultado
  • No tiene conocimiento de qué otros servicios participan en el proceso

Ventajas: Bajo acoplamiento, alta escalabilidad, sin punto único de fallo.

Desventajas: Flujo implícito, difícil depuración, complejidad en el rastreo.

2.2 Patrón Complementario: Orquestación Ligera de Estado

Se permite que UN servicio actúe como Coordinador de Estado (no como orquestador tradicional) con las siguientes restricciones:

Permitido:

  • Mantener una tabla de estado que rastree el progreso de la saga
  • Escuchar todos los eventos relevantes del flujo
  • Tomar decisiones de cancelación basadas en eventos recibidos
  • Emitir comandos de compensación cuando sea necesario
  • Proveer endpoints de consulta del estado de la transacción

Prohibido:

  • Invocar directamente (HTTP/gRPC) a otros servicios para ejecutar pasos
  • Mantener lógica de negocio que corresponde a otros dominios
  • Actuar como proxy o gateway entre servicios

Clarificación: El coordinador observa y reacciona, no comanda y espera. La comunicación sigue siendo asíncrona mediante el bus de eventos.

2.3 Prohibiciones Absolutas

  • Two-Phase Commit (2PC): No se permite debido a bloqueos prolongados y baja disponibilidad
  • XA Transactions: Prohibido extender transacciones de base de datos entre servicios
  • Distributed Locks: No se permiten bloqueos compartidos entre servicios (excepto Semantic Locks de negocio, ver Parte III)
  • Llamadas Síncronas en Flujo Crítico: HTTP/REST/gRPC solo para consultas (queries), nunca para comandos (writes) en sagas

3. Comunicación y Mensajería

3.1 Intermediario Obligatorio

Toda comunicación entre pasos de una saga debe realizarse a través de un Message Broker con las siguientes características:

Requisitos mínimos del broker:

  • Persistencia en disco (durabilidad de mensajes)
  • Garantía de entrega “al menos una vez” (at-least-once delivery)
  • Capacidad de reintento automático
  • Soporte para Dead Letter Queues (DLQ)
  • Ordenamiento por partición (opcional pero recomendado)

Brokers aprobados: Kafka, RabbitMQ, Amazon SQS/SNS, Azure Service Bus, Google Pub/Sub.

3.2 Topología de Mensajería

Para eventos de dominio:

  • Utilizar patrón Publish-Subscribe (pub/sub)
  • Múltiples consumidores pueden suscribirse al mismo evento
  • Los productores no conocen a los consumidores

Para comandos directos (casos excepcionales):

  • Utilizar colas punto-a-punto
  • Un solo consumidor procesa el mensaje
  • Incluir timeout de procesamiento

3.3 Restricciones de Comunicación Síncrona

Prohibido para:

  • Ejecutar el siguiente paso de una transacción crítica
  • Confirmar operaciones de escritura entre servicios
  • Propagar cambios de estado en flujos transaccionales

Permitido para:

  • Consultas de solo lectura (queries)
  • Validaciones previas no bloqueantes
  • Obtención de datos de referencia
  • Healthchecks y monitoreo

4. Transactional Outbox Pattern (Obligatorio)

4.1 Definición del Problema

El Dual Write Problem ocurre cuando un servicio intenta:

  1. Actualizar su base de datos local
  2. Publicar un evento en el broker

Si la publicación falla después del commit de BD, el sistema queda inconsistente. Si falla antes, se pierde el evento.

4.2 Solución Mandatoria

Regla de Oro: Un servicio nunca debe publicar directamente en el broker dentro de su código de negocio.

Implementación del patrón:

Primera fase - Transacción Atómica Local:

  1. Iniciar transacción de base de datos
  2. Ejecutar operación de negocio (INSERT/UPDATE/DELETE en tablas de dominio)
  3. Insertar el evento a publicar en una tabla especial llamada OUTBOX
  4. Confirmar transacción completa (COMMIT atómico)

Segunda fase - Publicación Asíncrona: 5. Un proceso independiente (Relay/Publisher) lee continuamente la tabla OUTBOX 6. Publica los eventos pendientes en el broker 7. Marca los eventos como publicados o los elimina

Tabla OUTBOX - Estructura requerida:

Campos obligatorios:

  • Identificador único del mensaje (UUID)
  • Tipo de evento (nombre del evento de dominio)
  • Cuerpo del evento (payload serializado)
  • Timestamp de creación
  • Estado de publicación (pendiente, publicado, fallido)
  • Número de intentos de publicación
  • Agregado raíz asociado (para ordenamiento)
  • Versión del esquema del evento

4.3 Estrategias de Relay

Opción A - Polling:

  • Proceso que consulta periódicamente la tabla OUTBOX
  • Publica eventos pendientes ordenados por timestamp
  • Marca como publicados tras confirmación del broker
  • Intervalo recomendado: 100-500 milisegundos

Opción B - Change Data Capture (CDC):

  • Herramienta que lee el transaction log de la base de datos
  • Detecta inserts en OUTBOX en tiempo real
  • Publica automáticamente en el broker
  • Ejemplos: Debezium, Maxwell, AWS DMS

Opción C - Database Triggers:

  • Trigger que se activa al insertar en OUTBOX
  • Invoca procedimiento que publica en broker
  • No recomendado por acoplamiento y menor resiliencia

5. Resiliencia e Idempotencia

5.1 Principio de Idempotencia

Dado que los brokers garantizan entrega “al menos una vez”, es inevitable que algunos mensajes se entreguen duplicados. Todo consumidor de eventos DEBE ser idempotente.

Definición: Una operación es idempotente si ejecutarla múltiples veces produce el mismo resultado que ejecutarla una sola vez.

Verificación obligatoria: Antes de procesar un evento, el consumidor debe verificar si el identificador del mensaje ya fue procesado previamente.

5.2 Implementación de Deduplicación

Tabla de Registro de Mensajes Procesados:

Cada servicio debe mantener una tabla dedicada con:

  • Identificador del mensaje (clave primaria)
  • Tipo de evento procesado
  • Timestamp de procesamiento
  • Estado final (éxito/fallo)
  • Índice en timestamp para limpieza periódica

Flujo de procesamiento idempotente:

  1. Recibir mensaje del broker
  2. Iniciar transacción de base de datos local
  3. Intentar insertar el identificador del mensaje en la tabla de registro
  4. Si la inserción falla por duplicado: hacer rollback y retornar éxito (ya fue procesado)
  5. Si la inserción es exitosa: ejecutar lógica de negocio
  6. Insertar evento resultante en tabla OUTBOX (si aplica)
  7. Confirmar transacción completa
  8. Enviar ACK al broker

Política de limpieza: Eliminar registros con más de siete días de antigüedad mediante proceso nocturno.

5.3 Estrategias de Compensación

Para toda operación de escritura que modifique estado de negocio, el servicio debe implementar una Transacción Compensatoria.

Definición: Acción lógicamente inversa que deshace (o mitiga) el efecto de una operación previamente confirmada.

Ejemplos de compensación:

Operación original → Compensación:

  • CrearPedido → AnularPedido
  • ReservarInventario → LiberarReserva
  • CobrarPago → ReembolsarPago
  • EnviarNotificacion → EnviarNotificacionCorreccion
  • AsignarRecurso → DesasignarRecurso

Características de compensaciones:

  • Debe ser idempotente (puede ejecutarse múltiples veces)
  • Puede ser semántica (no necesariamente restaura estado exacto)
  • Debe registrarse en logs de auditoría
  • Debe emitir eventos de compensación para trazabilidad

Tipos de compensación:

  • Compensación perfecta: Restaura el estado exacto anterior (ej: cancelar reserva)
  • Compensación aproximada: Restaura un estado equivalente (ej: reembolso en créditos en vez de dinero)
  • Compensación simbólica: Registra el intento de reversión cuando la compensación real es imposible (ej: no se puede “des-enviar” un email, pero se envía corrección)

5.4 Manejo de Errores y Reintentos

Clasificación de fallos:

Fallos Transitorios: Errores temporales que pueden resolverse reintentando

  • Pérdida de conexión de red
  • Timeouts de base de datos por carga
  • Servicio dependiente temporalmente no disponible
  • Límites de rate limiting

Fallos Permanentes: Errores que no se resolverán reintentando

  • Validaciones de negocio fallidas
  • Datos malformados o incompletos
  • Violaciones de reglas de dominio
  • Permisos insuficientes
  • Recursos no encontrados

Estrategia de Reintentos - Exponential Backoff:

Para fallos transitorios se debe implementar:

  • Espera inicial entre primer y segundo intento: 500 milisegundos
  • Multiplicador exponencial: factor de 2
  • Espera máxima entre intentos (techo): 60 segundos
  • Número máximo de intentos: 5
  • Jitter aleatorio: añadir variación del 10-25% para evitar thundering herd

Progresión ejemplo: 500ms → 1s → 2s → 4s → 8s → DLQ

Dead Letter Queue (DLQ):

Después de agotar los reintentos, el mensaje debe enviarse a una cola especial para:

  • Análisis manual posterior
  • Alertas al equipo de operaciones
  • Posible reprocesamiento manual tras corrección
  • Auditoría de fallos recurrentes

Propiedades requeridas en mensajes de DLQ:

  • Mensaje original completo
  • Número de intentos realizados
  • Timestamps de cada intento
  • Detalles de cada error ocurrido
  • Trace completo del último error

6. Versionado y Evolución de Contratos

6.1 Esquemas Explícitos Obligatorios

Todo evento de dominio publicado en el bus debe tener un esquema formal que defina:

  • Nombre y tipo de cada campo
  • Campos obligatorios vs opcionales
  • Tipos de datos permitidos
  • Restricciones de validación
  • Descripción semántica de cada campo

Formatos aprobados: Avro, Protocol Buffers (Protobuf), JSON Schema.

Prohibido: Publicar eventos con estructura ad-hoc sin definición formal.

6.2 Versionado Semántico de Eventos

Todo evento debe incluir un campo de metadatos que indique su versión siguiendo el formato semántico: MAJOR.MINOR.PATCH

Ejemplo: “schema_version”: “1.2.0”

Interpretación de versiones:

  • MAJOR: Cambios incompatibles que requieren actualización del consumidor

    • Eliminar campos
    • Cambiar tipo de dato existente
    • Cambiar semántica del campo
    • Renombrar campos
  • MINOR: Cambios retrocompatibles que agregan funcionalidad

    • Agregar nuevos campos opcionales
    • Agregar nuevos valores a enumeraciones
    • Deprecar campos (sin eliminarlos)
  • PATCH: Correcciones menores sin impacto funcional

    • Corregir descripciones
    • Mejorar documentación
    • Correcciones de typos en nombres

6.3 Estrategias de Evolución

Para cambios ADITIVOS (Minor/Patch):

  • Agregar solo campos opcionales con valores por defecto
  • Los consumidores antiguos ignoran campos nuevos
  • Los productores nuevos deben tolerar consumidores antiguos
  • No requiere coordinación de despliegue

Para cambios BREAKING (Major):

  • Crear un nuevo tipo de evento con sufijo de versión
    • Ejemplo: “OrdenSolicitada_v2”
  • Mantener publicación dual por período de transición
    • El productor emite tanto evento v1 como v2
  • Los consumidores migran gradualmente a la nueva versión
  • Período mínimo de convivencia: 90 días calendario
  • Después del período, deprecar y eliminar versión antigua

Política de deprecación:

  1. Anunciar deprecación con 90 días de anticipación
  2. Añadir warnings en logs cuando se use versión antigua
  3. Publicar métricas de uso de versiones obsoletas
  4. Coordinar migración con todos los equipos consumidores
  5. Eliminar soporte solo cuando uso sea cero por 30 días

6.4 Registro Centralizado de Esquemas

Obligatorio: Mantener un Schema Registry centralizado que:

  • Almacena todas las versiones de esquemas de eventos
  • Valida compatibilidad antes de registrar nuevas versiones
  • Provee APIs para consulta programática de esquemas
  • Genera documentación automática de contratos
  • Permite validación en tiempo de runtime

Herramientas recomendadas: Confluent Schema Registry, AWS Glue Schema Registry, Apicurio Registry.


7. Observabilidad y Rastreabilidad

7.1 Rastreo Distribuido

Generación de Identificadores:

El servicio que inicia una saga debe generar los siguientes identificadores únicos:

  • Saga ID: Identificador global único (UUID versión 4) que representa toda la transacción distribuida. Se genera una sola vez al inicio y se propaga sin cambios.

  • Span ID: Identificador único para cada paso o evento individual dentro de la saga. Cada servicio que procesa genera su propio Span ID.

  • Parent Span ID: Referencia al Span ID del paso anterior, creando una jerarquía de trazas.

Propagación de Contexto:

Estos identificadores deben incluirse como headers/metadatos en todos los mensajes:

  • Nombre del header de Saga ID: “X-Saga-ID”
  • Nombre del header de Span ID: “X-Span-ID”
  • Nombre del header de Parent Span: “X-Parent-Span-ID”

Los servicios intermedios deben:

  • Preservar el Saga ID sin modificarlo
  • Generar su propio Span ID
  • Copiar el Span ID recibido como su Parent Span ID
  • Propagar estos tres valores en todos los eventos que emitan

7.2 Logging Estructurado

Formato Obligatorio:

Cada entrada de log relacionada con procesamiento de eventos de saga debe ser estructurada (no texto plano) e incluir los siguientes campos:

Campos mandatorios de contexto:

  • Identificador de saga (copiado del mensaje)
  • Tipo de evento procesado
  • Versión del esquema del evento
  • Nombre del servicio que genera el log
  • Timestamp en formato ISO-8601 con zona horaria UTC
  • Identificador del span actual
  • Identificador del span padre

Campos mandatorios de resultado:

  • Estado del procesamiento: RECEIVED, PROCESSING, SUCCESS, FAILED, COMPENSATING, COMPENSATED
  • Duración en milisegundos de la operación
  • Número de intento (para reintentos)

Campos opcionales pero recomendados:

  • Identificador de correlación de negocio (ej: número de orden)
  • Identificador del usuario o entidad que inició la transacción
  • Datos relevantes del payload (sin información sensible)
  • Detalles del error (en caso de fallo)
  • Nombre del nodo/instancia que procesó

Niveles de Log:

  • INFO: Inicio y fin exitoso de procesamiento de evento
  • WARN: Reintentos por fallos transitorios
  • ERROR: Fallos permanentes, envío a DLQ
  • DEBUG: Detalles de validaciones y decisiones de negocio

Ejemplo descriptivo de entrada de log:

Un log estructurado indicando que el servicio de Inventario procesó exitosamente un evento PagoExitoso en 150 milisegundos, este fue el primer intento, pertenece a la saga con ID alfa-123, el span actual es beta-456 hijo del span gamma-789, ocurrió el 7 de febrero de 2026 a las 10:30:00 UTC, procesó la versión 1.0 del evento, y resultó en éxito.

7.3 Métricas Requeridas

Todos los servicios participantes en sagas deben exponer las siguientes métricas en formato compatible con sistemas de monitoreo:

Métricas de duración:

  • Nombre: saga_duration_seconds
  • Tipo: Histogram
  • Etiquetas: tipo_de_saga, estado_final (success, failed, compensated)
  • Descripción: Tiempo total desde inicio hasta conclusión de la saga

Métricas de errores por paso:

  • Nombre: saga_step_errors_total
  • Tipo: Counter (contador acumulativo)
  • Etiquetas: nombre_servicio, tipo_evento, tipo_error
  • Descripción: Cantidad total de fallos al procesar eventos

Métricas de compensaciones:

  • Nombre: saga_compensations_total
  • Tipo: Counter
  • Etiquetas: nombre_servicio, razón_compensación
  • Descripción: Cantidad de transacciones compensatorias ejecutadas

Métricas de mensajes pendientes:

  • Nombre: outbox_pending_messages
  • Tipo: Gauge (valor instantáneo)
  • Etiquetas: nombre_servicio
  • Descripción: Cantidad de eventos en tabla OUTBOX pendientes de publicar

Métricas de mensajes en DLQ:

  • Nombre: dlq_messages_total
  • Tipo: Gauge
  • Etiquetas: nombre_servicio, tipo_evento
  • Descripción: Cantidad de mensajes en Dead Letter Queue por servicio

Formato de exportación: Prometheus, OpenMetrics, o CloudWatch.

7.4 Trazabilidad de Auditoría

Para procesos críticos de negocio se debe mantener:

  • Tabla de auditoría de saga con todos los cambios de estado
  • Timestamp de cada transición de estado
  • Razón del cambio (evento que lo provocó)
  • Usuario o sistema responsable del inicio
  • Datos relevantes de negocio (sin información sensible duplicada)
  • Retención mínima: según políticas regulatorias (típicamente 7 años)

8. Límites Operacionales y Timeouts

8.1 Parámetros Configurables Mandatorios

Todo servicio participante en sagas debe exponer y documentar los siguientes parámetros de configuración:

Reintentos:

  • Número máximo de intentos antes de enviar a DLQ

    • Valor mínimo permitido: 3 intentos
    • Valor recomendado: 5 intentos
  • Espera inicial entre primer y segundo intento (backoff inicial)

    • Valor mínimo permitido: 100 milisegundos
    • Valor recomendado: 500 milisegundos
  • Espera máxima entre intentos (techo de backoff)

    • Valor mínimo permitido: 30 segundos
    • Valor recomendado: 60 segundos

Timeouts de saga completa:

  • Tiempo máximo total para completar toda la saga
    • Valor mínimo permitido: 1 hora
    • Valor recomendado: 24 horas
    • Nota: Ajustar según naturaleza del proceso de negocio

Timeouts por paso individual:

  • Tiempo máximo de espera por respuesta de un paso
    • Valor mínimo permitido: 30 segundos
    • Valor recomendado: 5 minutos (300 segundos)

Estos valores deben ser configurables sin recompilar código (variables de entorno, archivos de configuración, configuration server).

8.2 Política de Timeouts

Para timeouts de paso individual:

Si un evento esperado no llega dentro del plazo configurado:

  1. El servicio coordinador (si existe) debe emitir un evento de timeout
  2. Nombre del evento: “StepTimedOut” o similar
  3. Incluir en el payload: saga_id, paso esperado, tiempo transcurrido
  4. Iniciar proceso de compensación
  5. Registrar en logs con nivel ERROR
  6. Incrementar métrica de timeouts

Para timeouts de saga completa:

Si la saga no se completa dentro del plazo total configurado:

  1. Ejecutar compensación automática de todos los pasos confirmados
  2. Marcar la saga con estado TIMED_OUT
  3. Emitir evento de saga expirada para auditoría
  4. Notificar al usuario/sistema iniciador del fallo
  5. Generar alerta para equipo de operaciones
  6. No eliminar datos de auditoría (mantener para análisis)

8.3 Monitoreo de Umbrales

Alertas obligatorias:

  • Si el porcentaje de sagas fallidas supera 5% en ventana de 15 minutos
  • Si el tiempo promedio de saga supera el doble del baseline histórico
  • Si la cantidad de mensajes en DLQ supera 10 por servicio
  • Si hay mensajes en OUTBOX pendientes por más de 10 minutos
  • Si una saga individual supera el 80% del timeout configurado

Niveles de severidad:

  • CRITICAL: Afecta flujos de negocio críticos (pagos, pedidos)
  • HIGH: Afecta funcionalidad importante pero no crítica
  • MEDIUM: Degradación de rendimiento sin pérdida de funcionalidad
  • LOW: Anomalías detectadas pero sin impacto inmediato

PARTE II: IMPLEMENTACIÓN DE REFERENCIA

1. Caso de Uso: Procesamiento de Órdenes con Validación Preventiva

Dominio de negocio: Sistema de comercio electrónico

Objetivo: Procesar una orden de compra asegurando disponibilidad de inventario antes de ejecutar el cobro, minimizando reembolsos por falta de stock.

Estrategia elegida: Check-Then-Act (Verificación antes de Acción Financiera)

Justificación: Reducir costos de transacciones bancarias fallidas y mejorar experiencia del cliente evitando cobros seguidos de reembolsos.

2. Definición del Flujo Transaccional

El proceso se divide en cuatro fases secuenciales:

Fase 1 - Intención:

  • Acción: Registro inicial de la orden en el sistema
  • Estado resultante: PENDIENTE_VALIDACION
  • Evento emitido: OrdenSolicitada

Fase 2 - Validación:

  • Acción: Consulta de disponibilidad de inventario sin reserva
  • Tipo de operación: Lectura (SELECT) sin bloqueos
  • Eventos posibles: StockVerificado o StockNoDisponible

Fase 3 - Cobro Condicional:

  • Acción: Ejecución de transacción financiera
  • Precondición: Solo si Fase 2 fue exitosa
  • Eventos posibles: PagoExitoso o PagoRechazado

Fase 4 - Asignación con Bloqueo:

  • Acción: Descuento definitivo de inventario
  • Tipo de operación: Escritura (UPDATE) con bloqueo pesimista
  • Eventos posibles: StockAsignado o FalloAsignacion

3. Servicios Participantes y Responsabilidades

3.1 Servicio: Gestor de Pedidos

Rol: Coordinador de estado (Orquestación Ligera)

Responsabilidades:

  • Recibir la solicitud inicial del cliente
  • Crear el registro de orden con estado inicial
  • Generar el Saga ID único
  • Emitir el evento OrdenSolicitada vía patrón Outbox
  • Escuchar eventos de progreso del resto de participantes
  • Mantener máquina de estados de la orden
  • Actualizar estado según eventos recibidos
  • Notificar al cliente sobre el resultado final

Transiciones de estado:

Estado PENDIENTE_VALIDACION al recibir:

  • StockVerificado → PENDIENTE_PAGO
  • StockNoDisponible → CANCELADA_SIN_STOCK (flujo termina)
  • Timeout de validación → CANCELADA_TIMEOUT

Estado PENDIENTE_PAGO al recibir:

  • PagoExitoso → PENDIENTE_ASIGNACION
  • PagoRechazado → CANCELADA_PAGO_RECHAZADO

Estado PENDIENTE_ASIGNACION al recibir:

  • StockAsignado → COMPLETADA (flujo exitoso)
  • FalloAsignacion → CANCELADA_CON_REEMBOLSO (requiere compensación)

Eventos que escucha:

  • StockVerificado
  • StockNoDisponible
  • PagoExitoso
  • PagoRechazado
  • StockAsignado
  • FalloAsignacion
  • ReembolsoEjecutado

Eventos que emite:

  • OrdenSolicitada (inicio del flujo)
  • OrdenCompletada (conclusión exitosa)
  • OrdenCancelada (conclusión con fallo)

3.2 Servicio: Gestor de Inventario

Rol: Validador y Ejecutor (participa en dos momentos diferentes)

Responsabilidades:

Momento 1 - Validación (Fase 2):

  • Escuchar evento OrdenSolicitada
  • Consultar disponibilidad actual en base de datos
  • Validar si existe stock suficiente para los ítems solicitados
  • NO realizar ninguna reserva ni modificación de datos
  • Emitir resultado de validación

Momento 2 - Asignación (Fase 4):

  • Escuchar evento PagoExitoso
  • Iniciar transacción de base de datos con bloqueo pesimista
  • Re-verificar disponibilidad actual (puede haber cambiado desde Fase 2)
  • Descontar las unidades si aún hay stock disponible
  • Confirmar transacción o hacer rollback según resultado
  • Emitir resultado de asignación

Lógica de validación (Momento 1):

Para cada ítem en la orden:

  • Consultar tabla de productos con el SKU solicitado
  • Leer campo de cantidad disponible actual
  • Comparar cantidad solicitada vs cantidad disponible
  • Si para TODOS los ítems hay stock suficiente: emitir StockVerificado
  • Si para ALGÚN ítem no hay stock suficiente: emitir StockNoDisponible con detalles

Lógica de asignación (Momento 2):

  1. Iniciar transacción con nivel de aislamiento REPEATABLE_READ o superior
  2. Aplicar bloqueo pesimista (SELECT FOR UPDATE) sobre los productos afectados
  3. Re-leer cantidad disponible actual
  4. Validar nuevamente que hay stock suficiente
  5. Si validación exitosa:
    • Ejecutar UPDATE restando las unidades
    • Insertar evento StockAsignado en tabla OUTBOX
    • COMMIT de transacción
  6. Si validación falla (race condition, stock consumido por otra transacción):
    • Insertar evento FalloAsignacion en tabla OUTBOX
    • COMMIT de transacción (el evento de fallo debe publicarse)

Eventos que escucha:

  • OrdenSolicitada (trigger de validación)
  • PagoExitoso (trigger de asignación)

Eventos que emite:

  • StockVerificado
  • StockNoDisponible
  • StockAsignado
  • FalloAsignacion

Compensación: Si recibe evento de compensación (por fallo en paso posterior):

  • Restaurar las unidades de inventario sumando la cantidad original
  • Emitir evento StockLiberado

3.3 Servicio: Procesador de Pagos

Rol: Intermediario financiero condicional

Responsabilidades:

Operación Normal:

  • Escuchar evento StockVerificado (NO OrdenSolicitada, para evitar cobros sin stock)
  • Extraer información de pago del payload del evento
  • Invocar API de pasarela de pagos externa (Stripe, PayPal, etc.)
  • Manejar respuesta de la pasarela
  • Emitir resultado de la operación financiera

Operación de Compensación:

  • Escuchar evento FalloAsignacion
  • Identificar la transacción financiera original asociada
  • Ejecutar reembolso o reversa en la pasarela de pagos
  • Emitir evento de confirmación de compensación

Lógica de cobro:

  1. Recibir evento StockVerificado
  2. Validar que evento no fue procesado previamente (idempotencia)
  3. Extraer datos: monto, método de pago, token de tarjeta, etc.
  4. Llamar API de pasarela con timeout de 30 segundos
  5. Si respuesta es exitosa:
    • Almacenar ID de transacción de la pasarela
    • Insertar evento PagoExitoso en OUTBOX con referencia de transacción
    • COMMIT
  6. Si respuesta es rechazo (fondos insuficientes, tarjeta inválida):
    • Insertar evento PagoRechazado en OUTBOX con código de error
    • COMMIT
  7. Si hay timeout o error de red:
    • Aplicar política de reintentos con exponential backoff
    • Tras agotar intentos: enviar a DLQ para revisión manual

Lógica de compensación:

  1. Recibir evento FalloAsignacion
  2. Extraer Saga ID para identificar la transacción financiera original
  3. Buscar en base de datos local el ID de transacción de la pasarela
  4. Llamar API de reembolso de la pasarela con el ID original
  5. Si reembolso exitoso:
    • Insertar evento ReembolsoEjecutado en OUTBOX
    • COMMIT
  6. Si reembolso falla:
    • Reintentar con backoff exponencial
    • Tras 5 intentos fallidos: enviar alerta crítica a equipo de finanzas
    • Marcar para reembolso manual

Eventos que escucha:

  • StockVerificado (trigger de cobro)
  • FalloAsignacion (trigger de compensación)

Eventos que emite:

  • PagoExitoso
  • PagoRechazado
  • ReembolsoEjecutado
  • FalloReembolso (para casos críticos)

4. Escenarios de Ejecución

4.1 Escenario A: Flujo Exitoso (Happy Path)

Contexto inicial:

  • Cliente solicita 1 unidad del producto SKU-123
  • Inventario actual: 10 unidades disponibles
  • No hay operaciones concurrentes

Secuencia de eventos:

  1. Cliente envía solicitud HTTP POST al endpoint de Pedidos

  2. Pedidos crea registro de orden con estado PENDIENTE_VALIDACION

  3. Pedidos genera Saga ID: “550e8400-e29b-41d4-a716-446655440000”

  4. Pedidos inserta en OUTBOX el evento OrdenSolicitada

  5. Relay de Pedidos publica evento en topic “order-events”

  6. Inventario consume evento OrdenSolicitada

  7. Inventario consulta: SELECT cantidad FROM productos WHERE sku = ‘SKU-123’

  8. Inventario verifica: 10 unidades disponibles, pedido requiere 1, verificación OK

  9. Inventario inserta en OUTBOX el evento StockVerificado

  10. Relay de Inventario publica evento en topic “inventory-events”

  11. Pedidos consume StockVerificado y actualiza estado a PENDIENTE_PAGO

  12. Pagos consume StockVerificado

  13. Pagos invoca API de pasarela: “Cobrar 50.00 USD a tarjeta terminada en 4242”

  14. Pasarela responde: “Aprobado, transaction_id: txn_abc123”

  15. Pagos inserta en OUTBOX el evento PagoExitoso con referencia txn_abc123

  16. Relay de Pagos publica evento en topic “payment-events”

  17. Pedidos consume PagoExitoso y actualiza estado a PENDIENTE_ASIGNACION

  18. Inventario consume PagoExitoso

  19. Inventario inicia transacción con: BEGIN; SELECT cantidad FROM productos WHERE sku = ‘SKU-123’ FOR UPDATE

  20. Inventario lee cantidad bloqueada: 10 unidades

  21. Inventario valida: 10 >= 1, OK

  22. Inventario ejecuta: UPDATE productos SET cantidad = cantidad - 1 WHERE sku = ‘SKU-123’

  23. Inventario inserta en OUTBOX el evento StockAsignado

  24. Inventario confirma: COMMIT

  25. Relay de Inventario publica evento en topic “inventory-events”

  26. Pedidos consume StockAsignado y actualiza estado a COMPLETADA

  27. Pedidos emite evento OrdenCompletada

  28. Pedidos envía notificación al cliente: “Tu orden ha sido confirmada”

Resultado final:

  • Orden completada exitosamente
  • Inventario reducido a 9 unidades
  • Cliente cobrado y notificado
  • Duración total: aproximadamente 2-5 segundos

4.2 Escenario B: Fallo Temprano (Sin Stock Disponible)

Contexto inicial:

  • Cliente solicita 5 unidades del producto SKU-456
  • Inventario actual: 0 unidades disponibles
  • Optimización: evitar cobro innecesario

Secuencia de eventos:

  1. Cliente envía solicitud HTTP POST al endpoint de Pedidos

  2. Pedidos crea registro de orden con estado PENDIENTE_VALIDACION

  3. Pedidos genera Saga ID: “7c9e6679-7425-40de-944b-e07fc1f90ae7”

  4. Pedidos inserta en OUTBOX el evento OrdenSolicitada

  5. Relay de Pedidos publica evento en topic “order-events”

  6. Inventario consume evento OrdenSolicitada

  7. Inventario consulta: SELECT cantidad FROM productos WHERE sku = ‘SKU-456’

  8. Inventario verifica: 0 unidades disponibles, pedido requiere 5, verificación FALLA

  9. Inventario inserta en OUTBOX el evento StockNoDisponible con detalles

  10. Relay de Inventario publica evento en topic “inventory-events”

  11. Pedidos consume StockNoDisponible

  12. Pedidos actualiza estado a CANCELADA_SIN_STOCK

  13. Pedidos emite evento OrdenCancelada con razón “stock insuficiente”

  14. Pedidos envía notificación al cliente: “Lo sentimos, el producto está agotado”

Comportamiento de Pagos:

  • El servicio de Pagos NO escucha el evento StockNoDisponible
  • Por tanto, NO se ejecuta ninguna operación financiera
  • No hay cargos ni reembolsos

Resultado final:

  • Orden cancelada rápidamente (en menos de 1 segundo)
  • Cliente no fue cobrado
  • Costo financiero: cero
  • Experiencia de cliente: transparente y honesta

Beneficio del patrón: Esta arquitectura evita aproximadamente el 95% de reembolsos comparado con estrategias de “cobrar primero, verificar después”.

4.3 Escenario C: Fallo Tardío (Race Condition)

Contexto inicial:

  • Cliente A solicita 1 unidad del producto SKU-789
  • Inventario actual: 1 unidad disponible
  • Evento concurrente: Cliente B también solicita 1 unidad del mismo producto

Secuencia de eventos para Cliente A:

  1. Pedidos A crea orden con Saga ID: “saga-aaa”

  2. Pedidos A emite OrdenSolicitada

  3. Inventario verifica disponibilidad para saga-aaa

  4. Inventario consulta: 1 unidad disponible

  5. Inventario emite StockVerificado para saga-aaa

  6. Pagos procesa cobro para saga-aaa

  7. Pasarela aprueba transacción: txn_xyz789

  8. Pagos emite PagoExitoso para saga-aaa

Evento concurrente (Cliente B):

Mientras el flujo de Cliente A está entre Fase 3 y Fase 4:

  • Pedidos B emite OrdenSolicitada para saga-bbb
  • Inventario verifica: aún hay 1 unidad, emite StockVerificado para saga-bbb
  • Pagos cobra a Cliente B: txn_def456
  • Pagos emite PagoExitoso para saga-bbb

Continuación para Cliente A (intenta asignar primero):

  1. Inventario consume PagoExitoso para saga-aaa

  2. Inventario inicia: BEGIN; SELECT cantidad FROM productos WHERE sku = ‘SKU-789’ FOR UPDATE

  3. Inventario lee cantidad: 1 unidad

  4. Inventario valida: 1 >= 1, OK

  5. Inventario ejecuta: UPDATE productos SET cantidad = 0

  6. Inventario inserta evento StockAsignado para saga-aaa

  7. Inventario confirma: COMMIT

  8. Pedidos A recibe StockAsignado

  9. Orden A finaliza con estado COMPLETADA

Continuación para Cliente B (llega segundo):

  1. Inventario consume PagoExitoso para saga-bbb

  2. Inventario inicia: BEGIN; SELECT cantidad FROM productos WHERE sku = ‘SKU-789’ FOR UPDATE

  3. Inventario lee cantidad: 0 unidades (ya fue consumida por Cliente A)

  4. Inventario valida: 0 >= 1, FALLA

  5. Inventario NO ejecuta UPDATE

  6. Inventario inserta evento FalloAsignacion para saga-bbb con razón “race condition”

  7. Inventario confirma: COMMIT (importante: confirmar para que evento se publique)

  8. Pagos consume FalloAsignacion para saga-bbb

  9. Pagos busca transacción original: txn_def456

  10. Pagos invoca API de pasarela: “Reembolsar txn_def456”

  11. Pasarela confirma: “Reembolso procesado, refund_id: rfnd_xyz”

  12. Pagos inserta evento ReembolsoEjecutado para saga-bbb

  13. Relay de Pagos publica evento

  14. Pedidos B consume ReembolsoEjecutado

  15. Pedidos B actualiza estado a CANCELADA_CON_REEMBOLSO

  16. Pedidos B envía notificación al Cliente B: “Tu pago ha sido reembolsado, el producto se agotó durante el proceso”

Resultado final:

  • Cliente A: orden completada, inventario asignado
  • Cliente B: orden cancelada, dinero reembolsado automáticamente
  • Sistema mantuvo consistencia a pesar de concurrencia
  • No hubo sobreventa (overselling)

Observación crítica: Este escenario demuestra por qué la verificación en Fase 2 es “optimista” (sin bloqueo) y la asignación en Fase 4 es “pesimista” (con bloqueo). El bloqueo temprano causaría alta contención. El bloqueo tardío minimiza ventana crítica.

5. Consideraciones de Implementación

5.1 Ordenamiento de Eventos

Problema: Los brokers garantizan orden dentro de una partición, no globalmente.

Solución para este caso de uso:

  • Particionar eventos por Saga ID
  • Todos los eventos de una misma saga van a la misma partición
  • Configurar clave de partición: Saga ID
  • Garantiza que eventos de UNA orden se procesan en orden
  • No importa el orden entre órdenes diferentes

5.2 Manejo de Duplicados

Ejemplo de deduplicación en Inventario:

Cuando llega evento PagoExitoso:

  1. Extraer Message ID del header: “msg-12345”
  2. Iniciar transacción
  3. Intentar: INSERT INTO processed_messages (message_id, event_type) VALUES (‘msg-12345’, ‘PagoExitoso’)
  4. Si falla por clave duplicada:
    • Significa que este mensaje ya fue procesado
    • Hacer ROLLBACK
    • Retornar ACK al broker (no reintentar)
    • Registrar en log: “Evento duplicado ignorado”
  5. Si inserción exitosa:
    • Proceder con lógica de asignación de stock
    • COMMIT incluye tanto la nueva fila en processed_messages como el UPDATE de inventario
    • Retornar ACK al broker

5.3 Consistencia de OUTBOX

Garantía crítica: El evento en OUTBOX y el cambio de estado de negocio deben confirmarse en la misma transacción atómica de base de datos.

Ejemplo en Pedidos al recibir StockVerificado:

Transacción única:

  1. BEGIN
  2. UPDATE orders SET status = ‘PENDIENTE_PAGO’ WHERE saga_id = ‘550e8400…’
  3. INSERT INTO outbox (event_type, payload, saga_id) VALUES (‘OrdenActualizada’, ’{…}’, ‘550e8400…’)
  4. COMMIT

Si falla el COMMIT, ningún cambio se persiste. Si se confirma, ambos cambios quedan guardados atómicamente.

5.4 Configuración de Timeouts por Servicio

Pedidos:

  • Timeout de validación de stock: 30 segundos
  • Timeout de pago: 60 segundos (APIs bancarias pueden ser lentas)
  • Timeout de asignación: 15 segundos
  • Timeout total de saga: 2 horas

Inventario:

  • Timeout de consulta de BD: 5 segundos
  • Timeout de bloqueo pesimista: 10 segundos

Pagos:

  • Timeout de llamada a pasarela: 30 segundos
  • Reintentos en pasarela: 3 intentos con backoff de 2-8-18 segundos
  • Timeout de reembolso: 60 segundos

PARTE III: PATRONES AVANZADOS (OPCIONAL)

1. Sagas de Larga Duración

Definición: Procesos que requieren más de una hora para completarse, típicamente por intervención humana, validaciones externas, o pasos asíncronos lentos.

Ejemplos:

  • Proceso de aprobación de crédito (requiere revisión manual)
  • Workflow de incorporación de empleado (múltiples pasos en días)
  • Proceso de compra B2B con aprobaciones corporativas
  • Integración con sistemas legacy batch que procesan nocturnamente

1.1 Desafíos Específicos

Problema 1: Estado en Memoria No es viable mantener el estado de la saga en memoria de un servicio durante horas o días. El servicio puede reiniciarse.

Problema 2: Escalabilidad Miles de sagas activas simultáneas durante días consumen recursos si no se gestionan apropiadamente.

Problema 3: Visibilidad Usuarios y operadores necesitan consultar el estado de procesos que toman días.

1.2 Estrategia de Implementación

Persistencia de Estado Explícita:

Crear una tabla dedicada para rastrear sagas activas:

Campos requeridos:

  • Identificador único de saga (PK)
  • Tipo de saga (ej: “AprobacionCredito”)
  • Estado actual (ej: “ESPERANDO_REVISION_MANUAL”)
  • Timestamp de inicio
  • Timestamp de última actualización
  • Paso actual en el flujo
  • Contexto de negocio (datos necesarios para reanudar)
  • Usuario o entidad propietaria
  • Fecha de expiración o timeout

Timers Persistentes:

En lugar de mantener timers en memoria, usar:

  • Scheduled jobs que consultan tabla de sagas periódicamente
  • Buscar sagas en estado de espera cuyo timeout ha expirado
  • Emitir eventos de timeout para reanudar o compensar
  • Ejemplo: Job cada 5 minutos revisa sagas con “expected_event_by” < NOW()

Endpoints de Consulta:

Exponer APIs REST para que usuarios consulten estado:

  • GET /sagas/saga-id/status → Retorna estado actual y progreso
  • GET /sagas/saga-id/history → Retorna todos los eventos y transiciones
  • POST /sagas/saga-id/cancel → Permite cancelación manual (ejecuta compensación)

1.3 Patrón de Reanudación

Caso de uso: Proceso pausado esperando aprobación humana.

Flujo:

  1. Saga llega a paso que requiere aprobación
  2. Servicio emite evento “AprobacionSolicitada”
  3. Servicio actualiza tabla de sagas: estado = “ESPERANDO_APROBACION”
  4. Se envía notificación a aprobador (email, dashboard, etc.)
  5. Servicio NO mantiene nada en memoria, libera recursos
  6. Horas o días después, aprobador toma decisión
  7. Sistema de UI/backoffice emite evento “AprobacionOtorgada” o “AprobacionRechazada”
  8. Servicio escucha evento, consulta estado de saga en tabla
  9. Servicio carga contexto de negocio desde tabla
  10. Servicio reanuda flujo desde el paso siguiente
  11. Servicio actualiza estado en tabla

Beneficio: El servicio es stateless, puede reiniciarse sin perder progreso.


2. Sub-Sagas Anidadas

Definición: Una saga que, como parte de uno de sus pasos, inicia otra saga completa e independiente.

Ejemplo:

  • Saga principal: “ProcesarCompraEmpresarial”
  • Paso 3 de la saga requiere: “ValidarCreditoProveedor”
  • ValidarCreditoProveedor es en sí una saga con múltiples pasos (consultar bureaus, validar referencias, aprobar monto)

2.1 Reglas de Anidación

Máximo permitido: 2 niveles de profundidad

  • Saga Nivel 0 (raíz)
  • Saga Nivel 1 (hija directa)
  • Prohibido: Saga Nivel 2 (nieta)

Razón: Complejidad exponencial de compensación. Si una saga de nivel 2 falla, hay que compensar nivel 2, luego nivel 1, luego nivel 0. El rastreo se vuelve intratable.

2.2 Responsabilidad de Compensación

Principio: La saga padre es responsable de compensar sagas hijas si el flujo general falla.

Ejemplo:

Saga Padre: ProcesarCompra

  • Paso 1: CrearOrden → OK
  • Paso 2: ValidarCredito (invoca sub-saga) → OK
  • Paso 3: EnviarMercancia → FALLA

Compensación:

  1. Saga Padre emite evento de compensación para Paso 3 (no aplica, nunca ocurrió)
  2. Saga Padre emite evento “CancelarValidacionCredito” para sub-saga
  3. Sub-saga ejecuta su propia compensación interna (liberar límite de crédito reservado)
  4. Saga Padre compensa Paso 1 (CancelarOrden)

Implementación:

La saga padre debe:

  • Mantener registro de todas las sub-sagas iniciadas (almacenar Sub-Saga IDs)
  • Al compensar, emitir eventos de compensación dirigidos a cada sub-saga
  • Esperar confirmación de compensación de sub-sagas antes de completar su propia compensación
  • Implementar timeout: si sub-saga no confirma compensación en X tiempo, alertar para intervención manual

2.3 Propagación de Contexto

Identificadores requeridos:

  • Saga ID del padre (Root Saga ID)
  • Saga ID de la hija (Child Saga ID)
  • Nivel de anidación (0 = raíz, 1 = hija)

Headers en eventos de sub-saga:

  • X-Root-Saga-ID: ID de la saga raíz
  • X-Parent-Saga-ID: ID de la saga que inició esta
  • X-Saga-Level: Nivel numérico de anidación

Uso en observabilidad: Permite visualizar jerarquía completa en herramientas de tracing:

  • Root Saga: ProcesarCompra-123
    • Child Saga: ValidarCredito-456
      • Event: ConsultarBureau
      • Event: ValidarReferencias
    • Event: EnviarMercancia

3. Semantic Lock (Bloqueo Semántico de Negocio)

Problema: Prevenir race conditions en recursos críticos sin recurrir a bloqueos pesimistas de base de datos que reducen throughput.

Ejemplo del problema: Dos usuarios intentan reservar el mismo asiento de avión simultáneamente. Sin bloqueo, ambos podrían ver “asiento disponible” y ambos intentar comprarlo.

3.1 Concepto de Reserva Soft

En lugar de modificar inmediatamente el estado del recurso, se marca como “reservado temporalmente” con:

  • Identificador de quién reservó (Saga ID)
  • Timestamp de expiración (TTL - Time To Live)

Diferencia con bloqueo tradicional:

  • Bloqueo tradicional (SELECT FOR UPDATE): Mantiene lock de base de datos hasta commit/rollback
  • Semantic Lock: Marca lógica en el dato que otros respetan, lock se libera automáticamente por TTL

3.2 Flujo de Tres Fases

Fase 1 - Reserva Soft (Check):

  • Servicio verifica disponibilidad del recurso
  • Si está disponible, marca como “reservado” con Saga ID y TTL de 5 minutos
  • Emite evento “RecursoReservado”
  • NO compromete definitivamente el recurso

Fase 2 - Operación Crítica (Execute):

  • Se ejecuta la operación costosa (ej: cobro de pago)
  • Si falla, la reserva expira automáticamente por TTL
  • Si tiene éxito, emite evento para confirmar

Fase 3 - Confirmación Hard (Commit):

  • Servicio escucha evento de éxito de operación crítica
  • Convierte reserva soft en asignación definitiva
  • Cambia estado de “reservado” a “vendido”
  • Limpia TTL

Fase Alternativa - Liberación Automática:

  • Si saga falla o expira, el TTL llega a cero
  • Job periódico (cada minuto) busca reservas expiradas
  • Cambia estado de “reservado” a “disponible”
  • Recurso queda libre para otros

3.3 Ejemplo Completo: Reserva de Asiento

Tabla de asientos:

Campos:

  • id_asiento (PK)
  • numero_asiento
  • estado: DISPONIBLE, RESERVADO, VENDIDO
  • reservado_por_saga_id (nullable)
  • reservado_hasta_timestamp (nullable)

Paso 1 - Validación y Reserva:

Servicio de Asientos escucha OrdenDeVueloSolicitada:

  1. Consultar: SELECT estado, reservado_hasta FROM asientos WHERE numero = ‘12A’
  2. Si estado = VENDIDO: emitir AsientoNoDisponible
  3. Si estado = RESERVADO AND reservado_hasta > NOW: emitir AsientoNoDisponible
  4. Si estado = DISPONIBLE OR (estado = RESERVADO AND reservado_hasta <= NOW):
    • UPDATE asientos SET estado = ‘RESERVADO’, reservado_por_saga_id = ‘saga-123’, reservado_hasta = NOW + 5 minutos
    • Emitir AsientoReservado
    • COMMIT

Paso 2 - Pago:

Servicio de Pagos procesa cobro (toma 30 segundos):

  • Si éxito: emite PagoExitoso
  • Si fallo: NO emite nada, saga expira

Paso 3 - Confirmación:

Servicio de Asientos escucha PagoExitoso:

  1. Verificar que saga_id coincide: SELECT reservado_por_saga_id FROM asientos WHERE numero = ‘12A’
  2. UPDATE asientos SET estado = ‘VENDIDO’, reservado_por_saga_id = NULL, reservado_hasta = NULL
  3. Emitir AsientoConfirmado
  4. COMMIT

Job de Limpieza (cada 1 minuto):

  1. SELECT numero FROM asientos WHERE estado = ‘RESERVADO’ AND reservado_hasta <= NOW
  2. Para cada asiento encontrado:
    • UPDATE asientos SET estado = ‘DISPONIBLE’, reservado_por_saga_id = NULL, reservado_hasta = NULL
    • Registrar en log: “Reserva expirada para asiento X de saga Y”

Ventajas de este patrón:

  • Alta concurrencia: No bloquea filas durante el pago
  • Auto-recuperación: Fallos liberan recursos automáticamente
  • Fairness: Primer solicitante obtiene reserva temporal
  • Sin deadlocks: No hay bloqueos de base de datos

Desventajas:

  • Complejidad adicional en lógica de negocio
  • Requiere job de limpieza confiable
  • Ventana de race condition muy pequeña (pero existe) en actualización de reserva

PARTE IV: GUÍAS DE IMPLEMENTACIÓN

1. Checklist de Desarrollo

Todo servicio que participe en una saga debe cumplir los siguientes requisitos antes de pasar a producción:

1.1 Persistencia y Mensajería

Verificar que:

  • Existe tabla OUTBOX con todos los campos mandatorios
  • Existe tabla de mensajes procesados para deduplicación
  • Existe proceso Relay que publica eventos desde OUTBOX al broker
  • El Relay se ejecuta con intervalo no mayor a 1 segundo
  • Todos los eventos incluyen campo schema_version
  • Todos los eventos tienen esquemas registrados en Schema Registry

1.2 Idempotencia

Verificar que:

  • Todo handler de evento verifica Message ID antes de procesar
  • La verificación y el procesamiento están en la misma transacción
  • Existe test automatizado que envía mismo mensaje 3 veces y verifica resultado único
  • Existe limpieza automática de tabla de mensajes procesados

1.3 Compensación

Verificar que:

  • Para cada operación de escritura existe handler de compensación
  • La compensación es idempotente (puede ejecutarse N veces)
  • Existen tests que verifican que compensación revierte el estado
  • La compensación emite evento de confirmación
  • La compensación se registra en logs de auditoría

1.4 Configuración

Verificar que:

  • Todos los parámetros de timeout son configurables externamente
  • Valores por defecto cumplen con mínimos del estándar
  • Configuración se carga al inicio y se valida
  • Cambios de configuración no requieren recompilación

1.5 Observabilidad

Verificar que:

  • Todos los logs de saga son estructurados (no texto plano)
  • Saga ID se propaga en todos los eventos emitidos
  • Todas las métricas mandatorias están implementadas
  • Existe dashboard de monitoreo con visualización de métricas
  • Existen alertas configuradas para casos anómalos

1.6 Testing

Verificar que:

  • Existen tests de happy path completo
  • Existen tests de cada escenario de compensación
  • Existen tests de race conditions simuladas
  • Existen tests de chaos engineering (matar servicio en medio de saga)
  • Existe test de duplicación de mensaje

2. Templates de Eventos Estándar

Todos los eventos deben seguir una estructura consistente para facilitar consumo y rastreo.

2.1 Estructura Base de Evento

Todo evento publicado debe incluir tres secciones:

Sección 1 - Metadatos de Rastreo:

  • Identificador único del mensaje (UUID)
  • Identificador de la saga (UUID)
  • Identificador del span actual (UUID)
  • Identificador del span padre (UUID o null si es raíz)
  • Versión del esquema del evento (formato semántico)
  • Timestamp de creación en UTC ISO-8601
  • Nombre del servicio que emitió el evento
  • Tipo de evento (nombre descriptivo)

Sección 2 - Datos de Negocio (Payload):

  • Información específica del dominio
  • Solo datos necesarios para los consumidores
  • Sin información sensible sin encriptar
  • Con tipos de datos explícitos

Sección 3 - Metadatos Adicionales (Opcional):

  • Identificador de correlación de negocio
  • Identificador del usuario que inició el flujo
  • Información de contexto relevante
  • Tags o labels para filtrado

2.2 Ejemplo Descriptivo: Evento OrdenSolicitada

Metadatos de rastreo:

  • message_id contiene un UUID único generado al crear el evento
  • saga_id contiene el identificador de la saga completa de procesamiento de orden
  • span_id contiene un UUID único para este evento específico
  • parent_span_id contiene null porque este es el evento inicial
  • schema_version contiene el string “1.0.0”
  • timestamp contiene la fecha y hora de creación en formato ISO-8601 UTC
  • source_service contiene el string “pedidos”
  • event_type contiene el string “OrdenSolicitada”

Payload de negocio:

  • order_id contiene el identificador único de la orden en el sistema de pedidos
  • customer_id contiene el identificador del cliente que hizo la orden
  • items es una lista de objetos, cada uno contiene:
    • sku: código del producto
    • quantity: cantidad solicitada (número entero)
    • price: precio unitario (número decimal con dos decimales)
  • total_amount contiene el monto total de la orden (número decimal)
  • shipping_address es un objeto que contiene:
    • street: calle
    • city: ciudad
    • country: país
    • postal_code: código postal
  • payment_method es un objeto que contiene:
    • type: tipo de pago (string: “credit_card”, “paypal”, etc.)
    • token: token de pago tokenizado (NO el número de tarjeta real)

Metadatos adicionales:

  • correlation_id contiene un identificador de correlación de negocio (ej: número de orden visible al cliente)
  • user_id contiene el identificador del usuario autenticado
  • channel contiene el canal de origen: “web”, “mobile”, “api”

3. Estrategias de Testing

3.1 Tests de Unidad para Idempotencia

Objetivo: Verificar que procesar el mismo evento múltiples veces produce el mismo resultado.

Escenario de test:

  1. Preparar estado inicial de base de datos
  2. Crear un evento de prueba con Message ID específico
  3. Ejecutar handler del evento por primera vez
  4. Capturar estado resultante de base de datos
  5. Ejecutar handler del mismo evento (mismo Message ID) por segunda vez
  6. Verificar que estado de base de datos es idéntico al del paso 4
  7. Ejecutar handler por tercera vez
  8. Verificar nuevamente estado idéntico
  9. Verificar que tabla de mensajes procesados tiene solo UNA entrada para ese Message ID

Resultado esperado:

  • Operación de negocio se ejecutó solo una vez
  • Llamadas subsecuentes fueron ignoradas
  • No hubo duplicación de datos
  • No hubo errores

3.2 Tests de Compensación

Objetivo: Verificar que la transacción compensatoria revierte el efecto de la operación original.

Escenario de test para Inventario:

  1. Estado inicial: Producto SKU-999 tiene 100 unidades
  2. Publicar evento PagoExitoso para orden de 5 unidades de SKU-999
  3. Esperar procesamiento
  4. Verificar: Producto SKU-999 tiene 95 unidades
  5. Publicar evento FalloAsignacion (trigger de compensación)
  6. Esperar procesamiento de compensación
  7. Verificar: Producto SKU-999 tiene 100 unidades nuevamente
  8. Verificar en logs: Existe entrada de compensación ejecutada
  9. Verificar: Se emitió evento StockLiberado

Casos adicionales a probar:

  • Compensar cuando la operación original nunca ocurrió (compensación debe ser no-op)
  • Compensar dos veces (debe ser idempotente)
  • Compensar cuando el recurso ya no existe (debe manejarse gracefully)

3.3 Tests de Chaos Engineering

Objetivo: Verificar resiliencia ante fallos de infraestructura.

Escenario 1 - Matar Servicio Después de COMMIT:

  1. Iniciar saga de prueba
  2. Instrumentar código para matar proceso inmediatamente después de COMMIT en base de datos pero ANTES de enviar ACK al broker
  3. Iniciar servicio
  4. Esperar que saga procese
  5. Servicio muere
  6. Verificar: Cambio en BD se persistió
  7. Verificar: Evento en OUTBOX se persistió
  8. Reiniciar servicio
  9. Verificar: Relay publica evento pendiente
  10. Verificar: Saga continúa normalmente

Escenario 2 - Desconectar Broker Durante Saga:

  1. Iniciar saga
  2. Cuando llegue al paso 2, simular desconexión de red al broker
  3. Verificar: Servicio reintenta con exponential backoff
  4. Verificar: Eventos quedan pendientes en OUTBOX
  5. Restaurar conexión después de 2 minutos
  6. Verificar: Relay publica eventos pendientes
  7. Verificar: Saga completa exitosamente

Escenario 3 - Latencia Extrema de Base de Datos:

  1. Simular latencia de 10 segundos en todas las queries de BD
  2. Iniciar saga
  3. Verificar: Timeouts se activan correctamente
  4. Verificar: Mensajes van a DLQ después de reintentos
  5. Verificar: Se emiten alertas apropiadas
  6. Verificar: No hay deadlocks ni procesos zombies

3.4 Tests End-to-End de Saga Completa

Objetivo: Verificar flujo completo en ambiente similar a producción.

Infraestructura de test:

  • Broker real (Kafka o RabbitMQ dockerizado)
  • Bases de datos reales por servicio (PostgreSQL dockerizado)
  • Los tres servicios corriendo (Pedidos, Inventario, Pagos)
  • Mock de pasarela de pagos externa

Test de Happy Path:

  1. Insertar datos de prueba en BD de Inventario: SKU-TEST con 50 unidades
  2. Enviar request HTTP POST a servicio de Pedidos: crear orden de 10 unidades de SKU-TEST
  3. Esperar máximo 10 segundos
  4. Verificar en BD de Pedidos: orden existe con estado COMPLETADA
  5. Verificar en BD de Inventario: SKU-TEST tiene 40 unidades
  6. Verificar en BD de Pagos: existe registro de transacción aprobada
  7. Verificar en logs: todos los eventos se emitieron en orden correcto
  8. Verificar en métricas: saga_duration_seconds registró tiempo total

Test de Fallo y Compensación:

  1. Configurar mock de pasarela para rechazar pagos
  2. Insertar datos: SKU-TEST2 con 20 unidades
  3. Enviar request: crear orden de 5 unidades de SKU-TEST2
  4. Esperar procesamiento
  5. Verificar: Orden en estado CANCELADA_PAGO_RECHAZADO
  6. Verificar: Inventario NO se modificó (sigue en 20 unidades)
  7. Verificar: NO existe registro de transacción en BD de Pagos
  8. Verificar en logs: evento PagoRechazado fue emitido

Test de Race Condition:

  1. Insertar datos: SKU-TEST3 con 1 unidad
  2. Lanzar DOS requests simultáneos: ambos piden 1 unidad de SKU-TEST3
  3. Esperar procesamiento
  4. Verificar: UNA orden en estado COMPLETADA
  5. Verificar: OTRA orden en estado CANCELADA_CON_REEMBOLSO
  6. Verificar: Inventario en 0 unidades (solo una asignación exitosa)
  7. Verificar en logs: evento FalloAsignacion emitido para segunda saga
  8. Verificar: Mock de pasarela recibió llamada de reembolso

ANEXO A: Architecture Decision Records (ADR)

ADR-001: Prohibición de Two-Phase Commit y XA Transactions

Fecha: 2026-02-01
Estado: Aceptado
Contexto:

En arquitecturas de microservicios distribuidos, la coordinación de transacciones entre múltiples servicios requiere decisiones sobre consistencia vs disponibilidad. El protocolo Two-Phase Commit (2PC) y las transacciones XA ofrecen atomicidad estricta pero con costos significativos.

Decisión:

Se prohíbe el uso de transacciones distribuidas bloqueantes (2PC, XA Transactions) en todo el ecosistema de microservicios. En su lugar, se adopta el patrón Saga con consistencia eventual.

Razones:

  • Disponibilidad: 2PC requiere que todos los participantes estén disponibles simultáneamente. Si un servicio está caído, toda la transacción se bloquea. En sistemas distribuidos con múltiples servicios, la probabilidad de que algún componente esté temporalmente no disponible es alta.

  • Latencia: El protocolo requiere múltiples roundtrips de red (prepare, vote, commit). Esto incrementa significativamente la latencia percibida por usuarios finales.

  • Bloqueos: Los recursos (filas de base de datos) quedan bloqueados durante todo el protocolo. En sistemas de alta concurrencia, esto reduce dramáticamente el throughput.

  • Complejidad Operacional: Requiere coordinador transaccional centralizado (Transaction Manager) que se convierte en punto único de fallo. La recuperación de fallos en 2PC es compleja y propensa a estados inconsistentes.

  • Escalabilidad Limitada: No escala horizontalmente bien porque el coordinador se convierte en bottleneck.

Consecuencias:

Positivas:

  • Mayor disponibilidad del sistema (cada servicio puede operar independientemente)
  • Mejor throughput en operaciones concurrentes (sin bloqueos prolongados)
  • Escalabilidad horizontal sin límites de coordinador central
  • Resiliencia ante fallos parciales (saga puede progresar aunque un servicio esté caído temporalmente)

Negativas:

  • Complejidad lógica incrementada (implementación de compensaciones)
  • Ventanas de inconsistencia temporal (datos pueden estar desincronizados por segundos o minutos)
  • Mayor complejidad en testing (necesidad de probar escenarios de compensación)
  • Debugging más complejo (flujo implícito, necesidad de rastreo distribuido)

Mitigaciones de Consecuencias Negativas:

  • Implementar Transactional Outbox Pattern para garantizar publicación de eventos
  • Establecer observabilidad robusta con rastreo distribuido
  • Documentar claramente lógica de compensación
  • Implementar idempotencia estricta en todos los consumidores
  • Definir límites de timeout para evitar sagas infinitas

ADR-002: Validación de Inventario Antes de Pago

Fecha: 2026-02-01
Estado: Aceptado
Contexto:

En sistemas de comercio electrónico, existen dos estrategias principales para manejar inventario en el flujo de compra:

  • Estrategia A (Pay-First): Cobrar primero, luego verificar/asignar inventario. Si no hay stock, reembolsar.
  • Estrategia B (Check-Then-Pay): Verificar inventario primero, luego cobrar solo si hay disponibilidad.

Decisión:

Se adopta la estrategia Check-Then-Pay: validar disponibilidad de inventario ANTES de ejecutar el cobro financiero.

Razones:

  • Costos Financieros: Cada transacción con pasarela de pagos tiene un costo (típicamente 2.9% + 0.30 USD). Los reembolsos también incurren en costos. La estrategia Pay-First genera costos innecesarios cuando el pedido finalmente se cancela por falta de stock.

  • Experiencia de Usuario: Los clientes perciben negativamente ser cobrados y luego reembolsados días después. Genera desconfianza y fricción. La estrategia Check-Then-Pay ofrece feedback inmediato sobre disponibilidad.

  • Complejidad Contable: Los reembolsos complican la contabilidad y reconciliación bancaria. Requieren procesos adicionales de seguimiento.

  • Carga en Soporte: Clientes cobrados y luego reembolsados generan tickets de soporte preguntando por el cargo temporal.

Trade-offs Considerados:

  • Ventana de Race Condition: Entre la validación (Fase 2) y la asignación (Fase 4), el inventario puede ser consumido por otra transacción concurrente. Esto resulta en que algunos pagos exitosos requieran reembolso de todos modos.

  • Latencia Adicional: Agregar paso de validación aumenta latencia total del flujo en aproximadamente 200-500ms.

Mitigación del Race Condition:

  • Implementar re-verificación con bloqueo pesimista en Fase 4
  • Implementar compensación automática de pago (reembolso) cuando ocurra race condition
  • Monitorear tasa de race conditions y ajustar inventario buffer si es muy alta

Consecuencias:

Positivas:

  • Reducción del 95% en costos de transacciones fallidas (según estudios de caso de e-commerce)
  • Mejor experiencia de usuario (feedback inmediato de falta de stock)
  • Menor carga operacional (menos reembolsos manuales)
  • Contabilidad más limpia

Negativas:

  • Posibilidad de race condition (aproximadamente 5% de casos en alta concurrencia)
  • Latencia adicional de 200-500ms por validación previa
  • Complejidad de implementar lógica de re-verificación con bloqueo

Métricas de Éxito:

  • Tasa de reembolsos por falta de stock < 5%
  • Latencia total de checkout < 3 segundos en percentil 95
  • Tasa de quejas de clientes por cobros incorrectos < 0.1%

ADR-003: Coreografía con Coordinador de Estado vs Coreografía Pura

Fecha: 2026-02-01
Estado: Aceptado
Contexto:

Las sagas pueden implementarse mediante dos patrones principales:

  • Coreografía Pura: Cada servicio escucha eventos relevantes y emite eventos de resultado sin conocer el flujo completo. No existe ningún componente central.

  • Orquestación: Un servicio centralizado (orquestador) coordina toda la saga invocando directamente a participantes y esperando respuestas.

  • Híbrido (Coreografía con Coordinador de Estado): Servicios se comunican vía eventos (coreografía) pero uno de ellos mantiene estado de la saga para visibilidad y decisiones de alto nivel.

Decisión:

Se adopta el patrón híbrido: Coreografía con Coordinador de Estado opcional. Se prohíbe orquestación tradicional con llamadas síncronas.

Razones a Favor de Coreografía:

  • Bajo acoplamiento entre servicios
  • Alta escalabilidad y resiliencia
  • Facilita evolución independiente de servicios
  • No hay punto único de fallo

Razones Contra Coreografía Pura:

  • Difícil rastrear estado completo de una saga
  • Complejo identificar en qué punto está una transacción de negocio
  • No hay lugar obvio para implementar timeouts de saga completa
  • Testing end-to-end más complejo

Razones Contra Orquestación Tradicional:

  • Orquestador se convierte en bottleneck
  • Alto acoplamiento (orquestador conoce todos los servicios)
  • Punto único de fallo
  • Dificulta escalabilidad horizontal

Solución Híbrida:

Permitir que el servicio iniciador (ej: Pedidos) actúe como Coordinador de Estado con restricciones:

  • Puede mantener tabla de estado de saga
  • Puede escuchar eventos de progreso
  • Puede tomar decisiones de compensación
  • NO puede invocar directamente a otros servicios
  • NO puede ejecutar lógica de negocio de otros dominios

Consecuencias:

Positivas:

  • Mantiene beneficios de coreografía (bajo acoplamiento, escalabilidad)
  • Provee visibilidad centralizada de estado de saga
  • Facilita implementación de timeouts
  • Simplifica queries de estado para usuarios
  • Facilita testing y debugging

Negativas:

  • Incremento leve de complejidad en servicio coordinador
  • Riesgo de que coordinador acumule lógica que debería estar en otros servicios (debe vigilarse en code reviews)

Reglas de Implementación:

  • El coordinador solo mantiene estado, no ejecuta lógica de negocio
  • Toda comunicación sigue siendo asíncrona vía eventos
  • El coordinador es stateless (estado persiste en BD, no en memoria)
  • Debe ser posible eliminar el coordinador sin romper el flujo (otros servicios siguen funcionando)

ADR-004: Outbox Pattern como Estándar Obligatorio

Fecha: 2026-02-01
Estado: Aceptado
Contexto:

Al trabajar con microservicios y messaging, existe el problema de Dual Writes: un servicio necesita actualizar su base de datos local Y publicar un evento en el broker. No existe transacción distribuida que abarque ambos sistemas.

Escenarios problemáticos sin Outbox:

  • Escenario 1: Servicio actualiza BD, luego intenta publicar en broker, pero broker está caído → Cambio persiste pero evento nunca se publica → Inconsistencia
  • Escenario 2: Servicio publica en broker exitosamente, luego intenta commit en BD, pero falla → Evento publicado pero cambio no persiste → Inconsistencia
  • Escenario 3: Servicio hace commit en BD, luego proceso muere antes de publicar → Evento perdido → Inconsistencia

Decisión:

Hacer obligatorio el uso de Transactional Outbox Pattern en todos los servicios que participen en sagas.

Razones:

  • Atomicidad Garantizada: La tabla OUTBOX está en la misma base de datos que las tablas de negocio. Un commit atómico garantiza que ambos (cambio de negocio + evento) se persistan juntos o ninguno se persista.

  • Resiliencia ante Fallos: Si el proceso muere después del commit pero antes de publicar, el Relay independiente eventualmente publicará el evento pendiente.

  • Simplicidad Conceptual: La lógica de negocio se simplifica: solo se preocupa por persistir en BD local. La publicación en broker es responsabilidad del Relay.

  • Debugging Facilitado: Todos los eventos a publicar quedan registrados en una tabla. Se puede auditar qué eventos se publicaron, cuándo, cuántos intentos tomó, etc.

Implementaciones Consideradas:

  • Opción A - Polling: Proceso que consulta tabla OUTBOX periódicamente

    • Pros: Simple de implementar, funciona con cualquier BD
    • Contras: Latencia adicional (según intervalo de polling)
  • Opción B - Change Data Capture (CDC): Herramienta lee transaction log de BD

    • Pros: Latencia mínima (casi real-time), no impacta rendimiento de BD
    • Contras: Requiere herramienta adicional (Debezium), complejidad operacional
  • Opción C - Triggers de BD: Trigger que publica al insertar en OUTBOX

    • Pros: Latencia cero
    • Contras: Acopla BD con broker, dificulta testing, problemas de resiliencia

Decisión de Implementación:

  • Opción A (Polling) es mandatoria para todos los servicios
  • Opción B (CDC) es recomendada para servicios críticos de alto volumen
  • Opción C (Triggers) está prohibida por acoplamiento

Consecuencias:

Positivas:

  • Cero pérdida de eventos
  • Garantía de atomicidad entre cambio de negocio y publicación
  • Resiliencia ante fallos de broker o red
  • Auditoría completa de eventos

Negativas:

  • Latencia adicional (100-500ms típicamente con polling)
  • Necesidad de proceso Relay adicional
  • Tabla OUTBOX crece y requiere limpieza
  • Complejidad adicional en infraestructura

Mitigaciones:

  • Optimizar intervalo de polling (100-500ms es aceptable)
  • Implementar limpieza automática de eventos publicados después de 7 días
  • Usar índices apropiados en tabla OUTBOX para queries eficientes
  • Monitorear tamaño de OUTBOX y alertar si crece anormalmente

ANEXO B: Glosario de Términos

BASE: Modelo de consistencia para sistemas distribuidos. Acrónimo de Basically Available (Básicamente Disponible), Soft state (Estado Suave), Eventually consistent (Eventualmente Consistente). Contrasta con ACID.

Broker de Mensajes: Sistema intermediario que facilita comunicación asíncrona entre servicios mediante colas y topics. Ejemplos: Kafka, RabbitMQ.

Change Data Capture (CDC): Técnica para detectar y capturar cambios en base de datos mediante lectura del transaction log. Usado para publicar eventos sin Dual Write Problem.

Compensación: Transacción lógicamente inversa que deshace o mitiga el efecto de una operación previamente confirmada. Ejemplo: Si se cargó una tarjeta, la compensación es reembolsar.

Consistencia Eventual: Propiedad de sistemas distribuidos donde, en ausencia de nuevas actualizaciones, eventualmente todas las réplicas convergerán al mismo estado. No garantiza cuándo ocurrirá.

Coreografía: Patrón de saga donde cada servicio escucha eventos relevantes y reacciona autónomamente sin coordinación central. Comparable a bailarines que siguen música sin director.

Correlation ID: Identificador único que se propaga a través de múltiples servicios y operaciones para rastrear una transacción de negocio completa en logs y métricas distribuidos.

Dead Letter Queue (DLQ): Cola especial donde se envían mensajes que fallaron repetidamente después de múltiples intentos de procesamiento. Permite análisis y reprocesamiento manual.

Dual Write Problem: Problema de consistencia que ocurre al intentar escribir en dos sistemas diferentes (ej: base de datos + message broker) sin transacción atómica que abarque ambos.

Exponential Backoff: Estrategia de reintentos donde el tiempo de espera entre intentos crece exponencialmente. Ejemplo: 1s, 2s, 4s, 8s, 16s. Previene sobrecarga durante fallos.

Idempotencia: Propiedad de una operación que puede ejecutarse múltiples veces sin cambiar el resultado más allá de la primera ejecución. Ejemplo: “Establecer X = 5” es idempotente, “Incrementar X” no lo es.

Message Broker: Ver Broker de Mensajes.

Orquestación: Patrón de saga donde un componente central (orquestador) coordina explícitamente todos los pasos invocando a participantes y esperando respuestas.

Outbox Pattern: Ver Transactional Outbox Pattern.

Race Condition: Situación donde el resultado de una operación depende del tiempo relativo de eventos concurrentes. Ejemplo: dos transacciones leyendo el mismo inventario antes de que cualquiera lo actualice.

Relay: Proceso que lee eventos de la tabla OUTBOX y los publica en el message broker. Puede ser implementado via polling o CDC.

Saga: Patrón de diseño para manejar transacciones distribuidas mediante secuencia de transacciones locales coordinadas por eventos, con compensaciones para revertir en caso de fallo.

Schema Registry: Servicio centralizado que almacena y versiona esquemas de eventos/mensajes. Permite validación de compatibilidad y generación de documentación.

Semantic Lock: Bloqueo lógico a nivel de negocio (no de base de datos) que marca un recurso como “reservado temporalmente” con TTL, permitiendo alta concurrencia.

Span: En rastreo distribuido, representa una unidad de trabajo individual. Una saga completa contiene múltiples spans (uno por cada paso/evento).

Timeout: Límite de tiempo máximo para esperar una respuesta o completar una operación. Previene esperas infinitas ante fallos.

Transactional Outbox Pattern: Patrón que resuelve Dual Write Problem insertando eventos en tabla local (OUTBOX) dentro de la misma transacción de negocio, para luego publicarlos asíncronamente.

TTL (Time To Live): Tiempo de vida de un recurso o dato después del cual expira automáticamente. Usado en Semantic Locks para liberar reservas no confirmadas.

Two-Phase Commit (2PC): Protocolo de transacciones distribuidas bloqueantes que garantiza atomicidad mediante fase de preparación y fase de commit. Prohibido en este estándar.


ANEXO C: Referencias y Recursos

Documentación Técnica Recomendada

Libros:

  • “Designing Data-Intensive Applications” por Martin Kleppmann - Capítulos 7-9 sobre transacciones distribuidas y consistencia
  • “Microservices Patterns” por Chris Richardson - Capítulo 4 completo sobre Sagas
  • “Building Microservices” por Sam Newman - Segunda edición, capítulo sobre workflows y consistencia

Papers Académicos:

  • “Sagas” por Hector Garcia-Molina y Kenneth Salem (1987) - Paper original que define el patrón
  • “Life beyond Distributed Transactions: an Apostate’s Opinion” por Pat Helland - Argumentos contra transacciones distribuidas
  • “Building on Quicksand” por Pat Helland y Dave Campbell - Fundamentos de consistencia eventual

Recursos Online:

Bibliotecas y Frameworks Recomendados

Para Java/JVM:

  • Eventuate Tram Saga Framework - Framework especializado en sagas con outbox pattern
  • Axon Framework - CQRS y Event Sourcing con soporte para sagas
  • Apache Camel - Integración con múltiples brokers y patrones de mensajería

Para .NET:

  • MassTransit - Framework de messaging con soporte nativo para sagas
  • NServiceBus - Bus de servicios con soporte para sagas de larga duración
  • Rebus - Bus de mensajes ligero con soporte para sagas

Para Node.js:

  • Moleculer - Framework de microservicios con soporte para sagas
  • NestJS con Bull - Soporte para colas y workflows complejos

Para Python:

  • Nameko - Framework de microservicios con soporte para eventos
  • Celery - Sistema de colas distribuidas con soporte para workflows

Herramientas de Observabilidad

Rastreo Distribuido:

  • Jaeger - Sistema open-source de rastreo distribuido
  • Zipkin - Rastreo distribuido con múltiples integraciones
  • AWS X-Ray - Servicio administrado de AWS para rastreo
  • Google Cloud Trace - Servicio de rastreo para GCP

Logging:

  • ELK Stack (Elasticsearch, Logstash, Kibana) - Stack completo de logging
  • Grafana Loki - Sistema de agregación de logs
  • Splunk - Plataforma enterprise de análisis de logs

Métricas:

  • Prometheus - Sistema de métricas con modelo pull
  • Grafana - Visualización de métricas
  • Datadog - Plataforma SaaS completa de observabilidad

Comunidades y Foros

  • CNCF Slack - Canal #microservices
  • Stack Overflow - Tag “saga-pattern” y “distributed-transactions”
  • Reddit r/microservices
  • DDD/CQRS Google Group

FIN DEL DOCUMENTO

Última Actualización: Febrero 2026
Próxima Revisión: Agosto 2026
Responsable: Equipo de Arquitectura Empresarial
Contacto: [email protected]

Related Posts

Cuándo Usar Colas de Mensajes en el Desarrollo de Software

Cuándo Usar Colas de Mensajes en el Desarrollo de Software

Las colas de mensajes son herramientas clave para construir sistemas distribuidos, escalables y tolerantes a fallos. En este artículo te comparto una guía con situaciones comunes donde su uso es altam

Leer más
RabbitMQ 2: Arquitectura y Enrutamiento Avanzado en RabbitMQ

RabbitMQ 2: Arquitectura y Enrutamiento Avanzado en RabbitMQ

En nuestro primer artículo, exploramos qué es RabbitMQ, por qué es fundamental para la comunicación asíncrona en sistemas distribuidos y cuáles son sus casos de uso típicos. Lo comparamos con una "ofi

Leer más
RabbitMQ 1: Introducción a RabbitMQ, El Corazón de la Mensajería Asíncrona

RabbitMQ 1: Introducción a RabbitMQ, El Corazón de la Mensajería Asíncrona

En el mundo del desarrollo de software moderno, especialmente con el auge de los microservicios y los sistemas distribuidos, la forma en que las diferentes partes de una aplicación se comunican es fun

Leer más
RabbitMQ 3: Configuración y Gestión de Colas en RabbitMQ

RabbitMQ 3: Configuración y Gestión de Colas en RabbitMQ

Después de entender qué es RabbitMQ y cómo sus Exchanges y Bindings dirigen los mensajes, llegamos a la Cola. La cola es fundamentalmente un buffer confiable: es el lugar donde los mensajes esperan su

Leer más
RabbitMQ 4: Robustez y Seguridad en RabbitMQ

RabbitMQ 4: Robustez y Seguridad en RabbitMQ

Hemos recorrido el camino desde la introducción a RabbitMQ y su papel en la mensajería asíncrona, pasando por su arquitectura, componentes de enrutamiento (Exchanges y Bindings), y la gestión detallad

Leer más
RabbitMQ 5: Consumo de Recursos, Latencia y Monitorización de RabbitMQ

RabbitMQ 5: Consumo de Recursos, Latencia y Monitorización de RabbitMQ

Hemos explorado la teoría detrás de RabbitMQ, su arquitectura, cómo enruta mensajes y cómo podemos construir sistemas robustos y seguros. Sin embargo, para operar RabbitMQ de manera efectiva en produc

Leer más
RabbitMQ 6: Alta Disponibilidad y Escalabilidad con Clustering en RabbitMQ

RabbitMQ 6: Alta Disponibilidad y Escalabilidad con Clustering en RabbitMQ

Hasta ahora, hemos hablado de cómo un nodo individual de RabbitMQ maneja mensajes, gestiona colas, y cómo monitorizar su rendimiento y seguridad. Sin embargo, para aplicaciones críticas que no pueden

Leer más
Kafka 1: Introducción a Apache Kafka, fundamentos y Casos de Uso

Kafka 1: Introducción a Apache Kafka, fundamentos y Casos de Uso

En el panorama tecnológico actual, los datos son el motor que impulsa la innovación. La capacidad de procesar, reaccionar y mover grandes volúmenes de datos en tiempo real se ha convertido en una nece

Leer más
Kafka 2: Arquitectura Profunda de Kafka, Topics, Particiones y Brokers

Kafka 2: Arquitectura Profunda de Kafka, Topics, Particiones y Brokers

En nuestro primer artículo, despegamos en el mundo de Apache Kafka, sentando las bases de lo que es esta potente plataforma de streaming de eventos y diferenciándola de los sistemas de mensajería trad

Leer más
Kafka 3: Productores y Consumidores, Configuración y Buenas Prácticas

Kafka 3: Productores y Consumidores, Configuración y Buenas Prácticas

Hemos navegado por los conceptos esenciales de Apache Kafka y desentrañado la arquitectura que reside bajo la superficie, comprendiendo cómo los Topics se dividen en Particiones distribuidas entre Bro

Leer más
Kafka 4: Procesamiento de Datos en Tiempo Real con Kafka Streams y ksqlDB

Kafka 4: Procesamiento de Datos en Tiempo Real con Kafka Streams y ksqlDB

En los artículos anteriores, hemos construido una sólida comprensión de Apache Kafka: qué es, por qué es una plataforma líder para streaming de eventos, cómo está estructurado internamente con Topic

Leer más
Spring WebFlux 1: Fundamentos Reactivos y el Corazón de Reactor

Spring WebFlux 1: Fundamentos Reactivos y el Corazón de Reactor

¡Hola, entusiasta del desarrollo moderno! 👋 En el vertiginoso mundo de las aplicaciones web, donde la escalabilidad y la eficiencia son reyes, ha surgido un paradigma que desafía el modelo tradicion

Leer más
Spring WebFlux 2: Alta Concurrencia sin Más Hilos

Spring WebFlux 2: Alta Concurrencia sin Más Hilos

¡Bienvenido de nuevo a nuestra inmersión en Spring WebFlux! 👋 En la primera parte de esta serie, exploramos el "por qué" de la programación reactiva, entendiendo los problemas del bloqueo y descubri

Leer más
Kafka 6: Despliegue, Seguridad y Optimización

Kafka 6: Despliegue, Seguridad y Optimización

Hemos explorado la arquitectura fundamental de Apache Kafka, la dinámica entre productores y consumidores, sus potentes capacidades para el procesamiento de flujos de datos y las herramientas que enri

Leer más
Spring WebFlux 3: Comunicación, Datos y Errores Reactivos

Spring WebFlux 3: Comunicación, Datos y Errores Reactivos

¡Continuemos nuestro viaje por el fascinante mundo de Spring WebFlux! En la Parte 1, sentamos las bases de la programación reactiva y exploramos Project Reactor, el corazón de WebFlux. En la **Pa

Leer más
Kafka 7: Patrones Avanzados y Anti-Patrones con Kafka

Kafka 7: Patrones Avanzados y Anti-Patrones con Kafka

Hemos recorrido un camino considerable en nuestra serie sobre Apache Kafka. Desde sus fundamentos y arquitectura interna hasta la interacción con productores y consumidores, las herramientas de proces

Leer más
Kafka 5: Más Allá del Core, Explorando el Ecosistema de Apache Kafka

Kafka 5: Más Allá del Core, Explorando el Ecosistema de Apache Kafka

Hemos navegado por las entrañas de Apache Kafka, comprendiendo su funcionamiento interno, la interacción entre productores y consumidores, e incluso cómo procesar datos en tiempo real con Kafka Stream

Leer más
Spring WebFlux 4: Comunicación Avanzada, Pruebas y Producción

Spring WebFlux 4: Comunicación Avanzada, Pruebas y Producción

La serie Spring WebFlux nos ha llevado a través de un viaje fascinante por el mundo de la programación reactiva, desde sus fundamentos y el poder de Project Reactor hasta la construcción de arquit

Leer más
Arquitectura DDD y Hexagonal: Construyendo Software para el Futuro

Arquitectura DDD y Hexagonal: Construyendo Software para el Futuro

En el dinámico mundo del desarrollo de software, la complejidad es el enemigo silencioso. Las aplicaciones crecen, los requisitos cambian y, sin una guía clara, el código puede convertirse rápidamente

Leer más
Arquitectura de Base de Datos para Identidad, Autenticación y Autorización (IAM)

Arquitectura de Base de Datos para Identidad, Autenticación y Autorización (IAM)

Cuando se habla de seguridad en el contexto de una aplicación, la conversación casi siempre gira en torno a las capas visibles: el cifrado en tránsito, las políticas de contraseñas, los tokens de aute

Leer más
Arquitectura distribuida y el abandono consciente de ACID

Arquitectura distribuida y el abandono consciente de ACID

En el mundo de los sistemas distribuidos, hay una verdad incómoda que enfrentamos tarde o temprano: no podemos tenerlo todo. La promesa de las transacciones ACID tradicionales —esa garantía tranquiliz

Leer más
Observabilidad sin Ruido: Diseñando un Sistema de Logs con AOP en Arquitecturas DDD

Observabilidad sin Ruido: Diseñando un Sistema de Logs con AOP en Arquitecturas DDD

Hay una tensión que todo equipo de desarrollo enfrenta tarde o temprano: la necesidad de saber qué está pasando dentro del sistema sin que esa necesidad contamine el código que lo hace funcionar. Los

Leer más
Arquitectura Modular por Contexto: Cuando la Teoría se Encuentra con la Realidad

Arquitectura Modular por Contexto: Cuando la Teoría se Encuentra con la Realidad

Has estado ahí. Es lunes por la mañana, abres el proyecto en tu IDE, y necesitas modificar cómo se procesa un pedido. Treinta minutos después, todavía estás navegando entre carpetas intentando encontr

Leer más