Skip to main content

ransactional Outbox Publisher

8.1 Problema a resolver​

Sin Transactional Outbox, es posible inconsistencia:

Solicitud guardada en PostgreSQL ✓
Fallo al publicar en Pub/Sub ✗
→ Resultado: solicitud huérfana, nunca se procesa en ERP.

8.2 Diseño del patrón​

8.3 Tabla outbox_event​

ColumnaTipoNota
id (PK)BIGINT/BIGSERIAL o UUIDSecuencial conviene para polling ordenado; UUID si es global
aggregate_typeVARCHARPURCHASE_ORDER_REQUEST
aggregate_idUUIDrequestId
event_typeVARCHARPURCHASE_ORDER_REQUESTED
payloadJSONBEnvelope canónico completo
partnerId, companyId, sourceSystemVARCHARIndexado para filtros y métricas
statusENUM/SMALLINT0=PENDING, 1=PUBLISHED, 2=PUBLISH_FAILED_RETRY, 3=IGNORED
created_atTIMESTAMPTZ
processed_atTIMESTAMPTZ NULL
retry_countSMALLINT DEFAULT 0
last_errorTEXT NULL
  • Índices: (status, created_at) para pick; (aggregate_id, event_type) unique TBD.
  • Unique: opcionalmente (aggregate_type, aggregate_id, event_type, retry_group) — pero para MVP basta con que processed_event controle duplicados.

8.4 Tabla processed_event (idempotent consumer + publisher)​

ColumnaTipo
event_id UUID (PK)eventId del envelope
processed_at TIMESTAMPTZ
consumer VARCHARoutbox-publisher o fusion-worker
request_id UUIDIndex

8.5 Selección de eventos pendientes​

BEGIN;

SELECT * FROM outbox_event
WHERE status = 0
ORDER BY created_at ASC
LIMIT 20
FOR UPDATE SKIP LOCKED;

-- para cada fila: publish Pub/Sub

UPDATE outbox_event
SET status = 1,
processed_at = now(),
retry_count = retry_count + 1
WHERE id = ?;

INSERT INTO processed_event (event_id, processed_at, consumer, request_id)
VALUES (?, now(), 'outbox-publisher', ?)
ON CONFLICT DO NOTHING;

COMMIT;

8.6 Retries y manejo de fallos de publicación​

CasoPolítica
Pub/Sub timeout/unavailableResilience4j Retry con exponential backoff.
Max attempts en memoria TBD 3-5Marcar status=PUBLISH_FAILED_RETRY; alerta; proceso normal en polling.
Lag persistente > TBD 5 minutosAlert on-call.
Evento inválido (JSON corrupto)Marcar IGNORED + audit severidad ALTA.

8.7 Consideraciones de despliegue​

  • Polling cadencia: cada 100-500ms (configurable). Alternativa: PostgreSQL LISTEN/NOTIFY para reducir latencia (sobre campo status). MVP: polling simple.
  • Concurrencia: múltiples réplicas; SKIP LOCKED garantiza que cada evento se bloquee por una sola instancia.
  • Complemento: métrica nexus_outbox_lag_seconds = now() - min(created_at) para eventos PENDING.
  • CDC (opcional fases posteriores): Debezium connector → Pub/Sub, pero incrementa complejidad; no MVP.