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
XadmApplyServiceabre a tx programaticamente (txOps.executeWrite) e commita antes de retornar; o enfileiramento (via o colaboradorMensageriaEnfileirador) roda dentro desseexecuteWrite—MensageriaService.enfileiraré@Transactional(REQUIRED) e junta na tx corrente. Rollback do apply → a linha do outbox não nasce (outbox transacional real).applyPut/applyDeleterecebem ocorrelationId(= 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
MdfeWatchBodycomopayload— 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
MdfeMudouEventtem dois produtores: o ingest (XadmService) e o writeback powersync (PowersyncController, writeback-006). O ingest enfileira in-tx; oMdfeWatchListenerdeixou de enviar e passou a enfileirar (pós-commit) para cobrir o produtor powersync. ORelayM2mda 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/doApplyDeletequando o apply toca estoque.EstoqueMudouEvent/EstoqueWebhookListenerremovidos (produtor único = ingest → órfãos após mover o enfileiramento pra tx). - encerramento é só ENTRADA/auditoria. O
SascarEncerramentoControllerchamaregistrarEntrada(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 emEncerramentoService, ver 0015). - Dispatch tipado (SPI
EnviadorMensagem), transporte provisório. Um bean portipono app (WatchEnviador,PokeEnviador) desserializa opayloade 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@Clientgerado quando o 006 landar, sem reabrir a spec. Bearer injetado no envio, nunca persistido na linha. - Tela
/admin/mensageria+ gate@xadm.com.brvêm prontas na lib; o app só habilita e reusa o principal do 003 via oSessionAuthenticationFetcher(que popula aAuthenticationcom o atributoemail). À época, local, sem adotarxadm-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 paraV26__mensagem_m2m.sql(V25 já ocupado pelaV25__formulas.sqldo kit PIED, em produção). Dep:br.com.xadm:xadm-mensageria:0.1.0(Forgejo Packages, leitura pública). Depende da adoção daxadm-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).