
Transactional Outbox explicado desde cero
Integrar sistemas suele plantear una pregunta incómoda: ¿cómo publico un evento solo si la transacción de negocio tuvo éxito? Escribir primero en la base y luego en una cola crea dual-write: si falla el segundo paso, los sistemas divergen. El Transactional Outbox resuelve esto guardando el evento en la misma transacción que el cambio de negocio.
El problema del dual-write
Imagina crear un pedido y notificar al servicio de inventario. Si insertas el pedido y luego publicas en Kafka, pueden ocurrir tres desastres silenciosos:
- El pedido se guarda; la publicación falla → inventario desactualizado.
- La publicación tiene éxito; el commit falla → inventario decrementado sin pedido.
- Reintentos duplican mensajes → consumidores deben ser idempotentes.
El outbox evita elegir cuál desastre prefieres al separar atomicidad local de entrega eventual.
Una transacción, dos escrituras locales
En la misma transacción de base de datos:
- se actualizan las tablas de negocio (
orders,payments, …); - se inserta una fila en
outboxconevent_type,payload,status = NEW.
Si la transacción hace commit, ambas cosas existen. Si aborta, ninguna existe. No hay ventana de inconsistencia local.
La tabla outbox es append-only en la práctica: el relay marca filas como enviadas, no las borra inmediatamente si quieres auditoría.
Relay / worker
Un proceso separado —Outbox Relay— ejecuta un bucle:
- Fetch: lee eventos
NEW(con bloqueo optimista oFOR UPDATE SKIP LOCKED). - Publish: envía al broker o event stream.
- Mark as sent: actualiza el estado a
SENT(o equivalente).
Si publish falla, el evento permanece NEW o pasa a FAILED con retry. At-least-once delivery es la expectativa realista; exactly-once end-to-end requiere idempotencia en consumidores.
Broker y consumidores
El diagrama muestra un message broker durable con orden por partición. Los consumidores —Order Service, Inventory Service, Analytics— procesan eventos de forma independiente.
La ilustración usa nombres genéricos. No afirma que este archivo utilice exactamente esos servicios ni ese broker concreto; describe el patrón arquitectónico.
Propiedades que importan
| Propiedad | Qué aporta |
|---|---|
| Atomicidad | Negocio + evento committed juntos. |
| Sin dual-write | Un solo commit local. |
| Retries | El relay reintenta publish sin reejecutar la lógica de negocio. |
| At-least-once | Duplicados posibles; consumidores idempotentes. |
| Observabilidad | La outbox es audit trail de lo pendiente y lo enviado. |
| Recuperación | Tras caída del relay, el backlog se procesa al reiniciar. |
Relación con migraciones
Durante convivencia entre PostgreSQL y Azure SQL, el outbox puede propagar cambios hacia el sistema destino sin acoplar la transacción OLTP a la latencia de sincronización remota. No es la única estrategia —batch y CDC existen— pero encaja cuando necesitas eventos de negocio explícitos con garantías transaccionales.
Límites
Outbox añade latencia (el evento no es instantáneo) y operación del relay (monitorización, dead letters, backpressure). No sustituye diseño de esquema de eventos ni contratos de versionado.
Pero para OLTP que debe notificar downstream con fiabilidad, es uno de los patrones más claros que conozco — y merece entenderse desde cero antes de elegir alternativas más pesadas.