Idempotencia, reintentos, DLQ y reproceso manual
10.1 Escenarios de duplicados cubiertos
| # | Escenario | Mecanismo de defensa |
|---|---|---|
| E1 | Cliente reenvía POST por timeout | X-Idempotency-Key + UNIQUE(partnerId, idempotencyKey) |
| E2 | Partner envía misma externalReference | UNIQUE(partnerId, companyId, documentType, externalReference) lógico o físico |
| E3 | Outbox publica dos veces (bug) | processed_event UNIQUE eventId |
| E4 | Pub/Sub at-least-once delivery | Idempotent Consumer + processed_event(eventId) |
| E5 | Worker reinicia antes de ack | processed_event + transaccionalidad |
| E6 | Fusion responde tarde; se agota ack deadline y Pub/Sub reentrega | Fusion idempotency key (TBD campo a usar requestId en flexfields) |
| E7 | Reproceso manual | Nuevo eventId; validación estado y restricciones de idempotencia ERP |
10.2 Restricciones UNIQUE recomendadas en PostgreSQL
| Constraint | Alcance | Comentario |
|---|---|---|
purchase_order_request.requestId (PK) | Global | UUID único. |
UNIQUE(partner_id, idempotency_key) | API ingestion | Bloquea duplicado cliente en CREATE. |
UNIQUE(event_id) en processed_event | Publisher/consumer | Duplicado de eventos. |
UNIQUE(partner_id, external_reference, company_id, document_type) | Domain | Duplicado documento origen; partial index WHERE external_reference IS NOT NULL. |
10.3 Política de reintentos
| Nivel | Mecanismo | Retry policy TBD |
|---|---|---|
| Call Oracle Fusion adapter | Resilience4j Retry + exponential backoff | maxAttempts=3, initialInterval=2s, multiplier=2.0, maxInterval=30s. |
| Pub/Sub subscription | Dead Letter Policy + retry policy | minimumBackoff=10s, maximumBackoff=10min, maxDeliveryAttempts= TBD 5..15 |
| Outbox Publisher (publish) | Resilience4j Retry | maxAttempts=5; si no, status=PUBLISH_FAILED_RETRY. |
| Idempotency Key (HTTP 409) | No retry automático; cliente decide si reintentar con nueva key |
10.4 Taxonomía de errores vs retry
| Categoría | ¿Retry? | Estado final sugerido | HTTP Code (si aplica) | Log level | Alerta |
|---|---|---|---|---|---|
| VALIDATION_ERROR | No | VALIDATION_FAILED (posible reproceso manual tras regla fix) | 422 | WARN | Opcional por rule spike |
| BUSINESS_ERROR | No | REJECTED / FAILED | 422 / 409 | WARN | TBD |
| TEMPORARY_ERROR | Sí (hasta máximo) | RETRY_PENDING → FAILED → DLQ | 502 / 504 | ERROR | Sí |
| INTEGRATION_ERROR (Fusion 4xx funcional) | No | FAILED (posible reproceso manual) | 422 | ERROR | Sí |
| AUTHENTICATION_ERROR | Solo si es token refresh | TEMPORARY_ERROR → FAILED | 401 | ERROR | Sí (client credentials rotos) |
| AUTHORIZATION_ERROR | No | FAILED (configuración) | 403 | ERROR | Sí |
| TECHNICAL_ERROR | Sí (timeout DB? limitar) | TEMPORARY → FAILED | 500 | ERROR | Sí |
| NON_RETRYABLE_ERROR | No | FAILED → DLQ | 422 / 500 | FATAL | Sí |
10.5 DLQ
- Topic:
nexus.purchase-order.dlq - Atributos mínimo:
requestId,eventId,partnerId,original_topic,original_subscription,attempts,error_code,error_category. - Operación: Operador con role
OPERATOR→ APIPOST /requests/{requestId}/retry(validaciones: role, permisos partner, estado, evita duplicados, genera nuevo eventId, registra auditoría).
10.6 Reproceso manual
10.7 Inbox Pattern (evaluación)
Se evalúa no introducir Inbox adicional en MVP pues:
- Pub/Sub ack mode manual + processed_event ya cubren idempotencia del consumer.
- Outbox + DLQ + retry policy cubren e2e.
- Si aparece necesidad de reconciliación programada por lotes, agregar
inbox_eventsin duplicar esfuerzo.