Pular para conteúdo

0016 — Entrega garantida M2M: adota xadm-mensageria (outbox transacional)

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-08-07 · Decidido em: 2026-08-07

Contexto. O Integrador troca mensagens server-server nas duas pontas e nenhuma garantia hoje: o watch (→ int-sascar) e o poke de estoque (→ tradutor Thoms) disparavam fire-and-forget (virtual thread + @Client, falha logada e engolida); o encerramento recebido do int-sascar não tinha auditoria simétrica. Se o destino ficava offline, a mensagem se perdia até um novo ingest reincidir. A lib da casa xadm-mensageria (outbox transacional + relay + SPI + tela) resolve isso de forma reusável. Esta é a adoção da lib no Integrador. O como está em baixa-mdfe-sascar; aqui ficam as escolhas e o preterido.

Decisão.

  • Enfileirar DENTRO da transação do ingest, não pós-commit. O XadmApplyService abre a tx programaticamente (txOps.executeWrite) e commita antes de retornar; o enfileiramento (via o colaborador MensageriaEnfileirador) roda dentro desse executeWrite — MensageriaService.enfileirar é @Transactional (REQUIRED) e junta na tx corrente. Rollback do apply → a linha do outbox não nasce (outbox transacional real). applyPut/applyDelete recebem o correlationId (= request-id). Preterido: manter o listener pós-commit e só gravar a linha ali — perderia a garantia (a linha nasceria fora da tx de negócio).
  • watch: destilar no enfileiramento, salvando o MdfeWatchBody como payload — reenvio reprodutível sem reconsultar o espelho (que pode ter mudado). Preterido: salvar o item cru e re-destilar no envio.
  • Dois produtores de watch, um sender. O MdfeMudouEvent tem dois produtores: o ingest (XadmService) e o writeback powersync (PowersyncController, writeback-006). O ingest enfileira in-tx; o MdfeWatchListener deixou de enviar e passou a enfileirar (pós-commit) para cobrir o produtor powersync. O RelayM2m da lib vira o único sender; o disparo @Client à mão sai do caminho. Preterido: deixar o powersync best-effort (inconsistente) ou enfileirar in-tx no writeback (mais escopo/risco fora do que a feature pedia).
  • poke: payload mínimo (origem/slug/sinal) por ser corpo vazio; enfileira in-tx no doApplyPut/ doApplyDelete quando o apply toca estoque. EstoqueMudouEvent/EstoqueWebhookListener removidos (produtor único = ingest → órfãos após mover o enfileiramento pra tx).
  • encerramento é só ENTRADA/auditoria. O SascarEncerramentoController chama registrarEntrada (ENTRADA/RECEBIDO) — best-effort (try/catch): nunca altera o sempre-2xx nem a idempotência de negócio (o int-sascar é dono do retry; a baixa segue em EncerramentoService, ver 0015).
  • Dispatch tipado (SPI EnviadorMensagem), transporte provisório. Um bean por tipo no app (WatchEnviador, PokeEnviador) desserializa o payload e chama o transporte. Enquanto o contrato OpenAPI 006 não mergeia, os enviadores envolvem os @Client à mão atuais (SascarWatchClient/ TradutorWebhookClient) — já sob o relay durável; troca-se pelo @Client gerado quando o 006 landar, sem reabrir a spec. Bearer injetado no envio, nunca persistido na linha.
  • Tela /admin/mensageria + gate @xadm.com.br vêm prontas na lib; o app só habilita e reusa o principal do 003 via o SessionAuthenticationFetcher (que popula a Authentication com o atributo email). À época, local, sem adotar xadm-seguranca — o fetcher migrou para a lib depois, ver 0018. Distinta da tela /mdf (011): request/response das mensagens M2M × status ENCERRADA/ABERTA da MDF-e.
  • Schema owned pelo app. A lib não embarca Flyway; o DDL canônico (resource do jar db/mensagem_m2m.postgres.sql) foi copiado para V26__mensagem_m2m.sql (V25 já ocupado pela V25__formulas.sql do kit PIED, em produção). Dep: br.com.xadm:xadm-mensageria:0.1.0 (Forgejo Packages, leitura pública). Depende da adoção da xadm-comum-web (0017).

Consequências. Derrubar int-sascar/thoms e mudar uma MDF-e/estoque → a MensagemM2m nasce PENDENTE junto do commit do ingest; o relay tenta, marca ERRO/incrementa tentativas, e no teto vira PAUSADO + alarme; o operador reenvia/cancela em /admin/mensageria. Semântica AT-LEAST-ONCE (sob N instâncias o relay pode despachar em dobro — os receptores já deduplicam por chave natural; watch idempotente, encerramento por chave_nota). Um erro do enfileirar (ex. DB) agora propaga como qualquer escrita do ingest — não é engolido como no best-effort; é o preço de mover a garantia pra dentro da tx.

Fora de escopo: a lib xadm-mensageria em si (repo xadm-commons); o @Client gerado do 006 (transporte provisório à mão até lá); a migração da dir.B do encerramento (lado int-sascar); a tela /mdf (011); o XADM-push legado (segue sempre-200 sem outbox).