Pular para conteúdo

0012 — Convergência da entrega ao outbox da casa e adoção do micronaut-data

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-11 · Decidido em: 2026-08-24

Contexto

O int-pied garantia a entrega ao Integrador com um motor de outbox próprio (bespoke): FSM em pied_pedido.status, PushService (envio idempotente por content_hash, atômico por code via marcarEnviando), retry/backoff à mão. A casa extraiu essa capacidade para a lib xadm-mensageria (MensagemM2m + RelayM2m + SPI EnviadorMensagem + tela /admin/mensageria) — dois motores de outbox na casa. A convergência é débito de uniformidade (spec 004), não gap funcional.

Decisão

Converge a ENTREGA (não a FSM de negócio) para o outbox da casa; a FSM pied_pedido e a reconciliação ficam.

  • Separação domínio × entrega: pied_pedido.status segue a FSM de negócio; o PUSH ao Integrador vira uma MensagemM2m tipo="xadm-push". O PushEnviador (SPI EnviadorMensagem) faz o PUT; o RelayM2m da lib substitui o drain automático.
  • Idempotência preservada sobre at-least-once: o RelayM2m é at-least-once sem lock; o PushEnviador re-checa o content_hash antes do PUT, e o /api/v1/xadm do Integrador é upsert idempotente por chave (confirmado no XadmController) — dupla proteção.
  • Entrega × negócio no PUT sempre-200: o /api/v1/xadm responde 200 mesmo em resultado=ERRO. Logo 200 = entrega OK (ResultadoEnvio.ok, não retenta); o resultado de negócio (SUCESSO/ERRO) vai pro domínio (marcarEnviado/marcarErro). Só falha de transporte (timeout/5xx/401) → ResultadoEnvio.falha (o relay retenta). ERRO de negócio não auto-retenta.
  • Prontidão fica no domínio: o guard temInvoice (segura o envio até o REST preencher a invoice) fica no passo que enfileira — enfileira só o send-ready, sem penalizar o relay.

Consequência dura: adotar xadm-mensageria FORÇA micronaut-data

O MensagemM2mRepository da lib é @JdbcRepository → arrasta micronaut-data-jdbc. Com ele no classpath, o DataSource injetado vira transaction-aware, e o int-pied — raw-SQL por design (dataSource.getConnection() direto, "sem micronaut-data") — quebra em massa com "No current connection present". Isso força uma migração do padrão de persistência. O padrão (comprovado experimentalmente, 76 falhas → 0 no caminho crítico):

  1. annotationProcessor("io.micronaut.data:micronaut-data-processor") — o fix-chave. Sem ele, @Transactional fica não-processado (no-op): não há interceptor AOP → o acesso raw-SQL fora de transação quebra. É o que o int-pied nunca teve (nunca usou data).
  2. @Transactional class-level nos 13 repos raw-SQL — torna o app transaction-aware; o path atômico (marcarEnviando + enfileirar) compartilha a transação.
  3. Nos TESTES, desembrulhar o DataSource — o seed via dataSource.getConnection() direto é incompatível com o wrapper transaction-aware em ambos os modos de @MicronautTest (transactional=true esconde o seed do controller; transactional=false faz o próprio seed falhar). O fix: while (ds instanceof DelegatingDataSource d) ds = d.getTargetDataSource(); → seed committed, visível ao app. (transactional=false global, como os apps data-jdbc da casa, não basta aqui porque o seed é raw JDBC.)

Correção 2026-09-11: o "raw-SQL por design" acabou — a persistência inteira migrou para Micronaut Data (decisão 0022): os repositórios não abrem mais dataSource.getConnection(), o SQL é @Query nativo com o mesmo texto. Seguem valendo a convergência da entrega ao outbox, o annotationProcessor (item 1) e o @Transactional de classe nos repositórios (item 2, agora sobre a interface @JdbcRepository). O desembrulho do DataSource (item 3) continua só na BancoFixture de teste. SQL à mão ficou apenas na varredura em lote do NormalizadorService e no advisory lock do PiedClusterLock, guardados pelo FronteirasTest.

Status (2026-08-24)

Código completo e compilando. Gate sem-Docker verde (test unit + checkstyle + compileIntegrationTest); WebhookControllerTest de integração passou isolado, provando o unwrap. A suíte de integração completa não foi validada localmente — a máquina de dev degradou após horas (worker JVM morre com EOFException), problema de ambiente, não de código. Validação deferida ao CI (ci-java:25 limpo) ou a um check pós-reboot — decisão explícita (REGRA Nº3), não silêncio.

Pendências declaradas

  • Validação da suíte de integração no CI/ambiente limpo (o gate real).
  • Gate de segurança /admin/mensageria: a tela da lib nasce aberta (security.enabled=false); o gate @<dominio> fica pra quando a segurança ativar (deferida, gate de identidade TBD).
  • Blocking B2 (§6): o SPI EnviadorMensagem documenta @Client-gerado-de-OpenAPI; o int-pied usa client à mão contra o legado /api/v1/xadm — divergência aceita; a lib deveria rotular como aceitável ou prover adaptador.

Alternativas descartadas

  • Manter o outbox bespoke. Funciona, mas mantém dois motores na casa (débito de convergência).
  • @MicronautTest(transactional=false) global (padrão dos apps data-jdbc). Não resolve o seed raw-JDBC do int-pied — só o desembrulho do DataSource resolve.
  • Desembrulhar o DataSource na PRODUÇÃO. Quebraria a atomicidade do enfileiramento; o desembrulho é só nos testes, a produção fica transaction-aware.