R-004 — Despacho de eventos do outbox — 3 tentativas, sem distinção de erro

TLDR: OutboxEvent sai de pending para sent numa entrega bem-sucedida, ou para failed depois da 3ª falha — qualquer falha soma 1 tentativa, sem diferenciar erro retryable de definitivo. O reenvio manual (ActiveAdmin) zera attempts e volta o evento para pending.

Given / When / Then

Dado um OutboxEvent pending, Quando Outbox::DispatchEventsJob despacha e recebe uma resposta de sucesso (HTTP 2xx), Então o evento vira sent, com sent_at preenchido.

Dado um OutboxEvent pending com attempts menor que 2, Quando o despacho falha (HTTP não-2xx, exceção ou timeout), Então attempts soma 1, last_error é preenchido, e o evento continua pending — o job se reenfileira e tenta de novo em 5 segundos.

Dado um OutboxEvent pending com attempts igual a 2, Quando o despacho falha novamente (a 3ª tentativa), Então o evento vira failed — não há mais tentativa automática.

Dado um OutboxEvent failed, Quando um operador aciona “Reenviar” no ActiveAdmin, Então o evento volta para pending com attempts: 0 e last_error: nil — as 3 tentativas recomeçam do zero.

Tabela de decisão

Situação attempts antes Resultado do despacho status depois
Sucesso (2xx) qualquer sucesso sent
Falha (4xx, 5xx, exceção, timeout) 0 ou 1 falha pending, attempts + 1
Falha (4xx, 5xx, exceção, timeout) 2 falha failed, attempts: 3
Reenvio manual 3, failed — pending, attempts: 0

Restrições

  • Não há distinção entre erro retryable (429/5xx/timeout) e definitivo (4xx): as 3 tentativas valem para qualquer falha. Um erro de payload/mapeamento (ex.: company_not_found) também consome as 3 tentativas antes de ficar visível como failed.
  • O espaçamento entre tentativas é o retry_on do próprio job (5 segundos), não um campo na tabela (next_attempt_at). Cada evento tem o seu job: não há varredura periódica.
  • Sem alerta automático (Sentry, e-mail) na falha definitiva — a visibilidade é só pelo recurso outbox_events no ActiveAdmin (namespace: :system_manager).
  • A tabela e o job são genéricos: outros tipos de evento reutilizam o mecanismo registrando um despachante em Outbox::DispatchRegistry::DISPATCHERS, sem migração nova — já confirmado por três consumidores reais além do primeiro (accounts.purchase_create, accounts.installment_paid e accounts.installment_overdue), sem qualquer mudança na tabela, no model ou no job.
  • Um despachante atende mais de um event_name: os três eventos do accounts apontam para Outbox::Dispatchers::AccountsEvents, porque o destino é o mesmo endpoint (POST /api/v1/events) com os mesmos cabeçalhos — o que distingue um evento do outro é o event_type dentro do payload, não a classe que despacha.

Teste vinculado

spec/models/outbox_event_spec.rb (#register_failure!, #mark_sent!, #retry!) e spec/jobs/outbox/dispatch_events_job_spec.rb (transições via o job, incluindo a 3ª falha e o evento sem despachante registrado).

Referências