0008 — Adotar a lib xadm-mensageria (log de ENTRADA do poke)¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-11 · Decidido em: 2026-08-06
Contexto¶
O tradutor recebe um poke do integrador ("produto/estoque mudou", corpo vazio) no
WebhookController (POST /api/integrador/produto + alias /webhook/estoque-mudou) e dispara o
Sweep em virtual thread, respondendo 202 na hora. Esse poke era fire-and-forget e não deixava
rastro: não havia registro de quando o integrador chamou nem quantas vezes. Se o parceiro
reclama de estoque desatualizado, não dava para provar "o integrador me pokou às 07:03 e o sweep
rodou" — a evidência não existia.
Esta é a Fase 3 da iniciativa libs+contrato: a casa extraiu o motor de mensageria M2M para a lib
br.com.xadm:xadm-mensageria (pacote br.com.xadm.comum.mensageria). O thoms é a adoção mais
leve das três: é receptor puro nesta direção — só log de ENTRADA + tela, sem outbox de
saída.
Decisão¶
webstorm-ecom depende da xadm-mensageria (registro Maven Forgejo) — adotada na 0.1.0;
a versão corrente é a do build.gradle.kts (fonte única, hoje 0.4.0) e usa só o lado de
entrada:
- Registro do poke — o
WebhookControllerchamaMensageriaService.registrarEntrada(tipo="poke", correlationId, payload, resumo)síncrono no thread do request, antes de disparar o sweep. Grava umaMensagemM2mdirecao=ENTRADA/status=RECEBIDO. Falha do registro propaga (poke pode virar 5xx): o banco é compartilhado e o sweep também depende dele, então um202com o banco caído seria mentira. O202e o disparo do sweep ficam inalterados — o registro é aditivo. correlationId= UUID gerado (a casa não tem MDC de request-id; é o ramo fallback do contrato).payload= metadados saneados do recebimento (rota, instante, origem) — nunca oAuthorization/Bearer. Corpo do poke é vazio.- Tela
/admin/mensageria(read-only) — vem embarcada na lib (@Controller+mensageria.html); o thoms só a habilita. Lista as entradas; como o thoms não produzSAIDA, os botões de reenviar/cancelar (que a view só mostra em linhasSAIDA) não aparecem. - Tabela
mensagem_m2mé do INTEGRADOR — o tradutor NÃO a cria. Nodb_thomscompartilhado a tabela tem um dono só, e é o integrador (remetente M2M com outbox/relay). O tradutor consome (gravaENTRADAviaregistrarEntrada), sem migração de produção. Em teste, sem integrador, uma migração test-only (db/migration-test/V6__mensagem_m2m.sql, ligada só noapplication-test.yml) cria a tabela. Deploy coordenado: o tradutor-mensageria só sobe junto/depois do integrador-mensageria (release sincronizado dos repos) — senão oregistrarEntradagrava numa tabela inexistente. - Config (
application.yml, blocomensageria:) —relay.enabled: false,retencao-dias: 90,admin.email-domain: ${AUTH_XADM_EMAIL_DOMAIN:xadm.com.br}(ver Pontos notáveis).
Pontos notáveis (verificados contra o jar 0.1.0)¶
- Retenção — a lib PODA
RECEBIDO. A lib tem um@ScheduleddiárioLimpezaMensagensque apaga linhas[ENVIADO, RECEBIDO]mais velhas quemensageria.retencao-dias(default 30). ComoRECEBIDOé o status do poke, no default a auditoria sumiria em 30 dias — o oposto do valor da feature. Fixamosretencao-dias: 90(alinhawebstorm.cleanup.retencao-diasdo app). O número é decisão de negócio — confirmar a janela com a Thoms. - Dois
@Scheduled, não um. Desligar o relay (relay.enabled: false, cortado porque não háEnviadorMensagem/SAIDA) não desliga oLimpezaMensagens— este roda diário independentemente, governado porretencao-dias. - Gate: quem manda é a rule da lib. A
MensageriaAdminRuleda lib temgetOrder() = -100e precede aViewSecurityRuledo thoms (0): decide/admin/mensageriasozinha. Exige o atributoemaildo principal (populado peloSessionAuthenticationFetcherdoxadm-seguranca) terminando em@${mensageria.admin.email-domain}— sem esse property a rule tranca todos, inclusive o admin válido. Consequências: anônimo → 401; autenticado fora do domínio → 403; a tela não herda o bypass dev/test das demais/admin(a rule roda antes daViewSecurityRule). - Tabela compartilhada com o integrador.
mensagem_m2m(sem o prefixowebstorm_ecom_*— é infra da lib) é a mesma que o integrador cria/usa nodb_thoms: o tradutor grava as linhasENTRADA, o integrador asSAIDAdo outbox. Por isso o tradutor não a migra em produção — doisCREATE TABLEno mesmo banco colidiriam (o 2º deploy quebraria com "table already exists"). Coordenação de retenção: aLimpezaMensagensda lib (poda diária de[ENVIADO, RECEBIDO]) roda nos dois apps sobre a mesma tabela — alinhar no release sincronizado quem poda (idealmente só o dono/integrador) pra o tradutor não apagar asSAIDAdo integrador. - Bug corrigido no thoms — model imutável. A tela da lib devolve o model como
Map.of(...)imutável. OGlobalViewModel(oViewModelProcessorque injetacurrentUser/csrf/marca em toda view) faziamodel.putdireto →UnsupportedOperationException(500 no render, quebraria a tela em prod). Passou a enriquecer uma cópia mutável e re-injetar viasetModel— tolera model imutável vindo de qualquer controller de lib.
Fica de fora (não é escopo desta adoção)¶
- Outbox de saída / entrega garantida do thoms — o único outbound é o
POST /sync/erpna WebStorm (3º-parte externa, não par M2M da casa); semenfileirar, SPIEnviadorMensagemnem relay ativo. - Ações de reenviar/cancelar — o thoms não é remetente M2M nesta direção; tela read-only.
- Contrato OpenAPI do poke — trabalho distinto e paralelo (receptor publica o contrato).
Consequências¶
- Auditoria do poke existe e é consultável pelo operador
@xadm.com.br— fecha a lacuna de evidência da entrada M2M da casa. - Correções/evoluções do motor de mensageria passam a vir por bump de versão da lib, não por código local — mesmo racional de 0006/0007.
xadm-mensageriaestá em0.1.0(pré-1.0.0): esta é a primeira adoção; a API estabiliza quando assentar.
Alternativas descartadas¶
- Log caseiro do poke (tabela
webstorm_ecom_*própria + tela ad-hoc) — reinventa o que a lib já padroniza entre os adotantes; drift-prone. - Adotar o motor completo com outbox — o thoms não tem par M2M de saída da casa; ligar relay/SPI/
máquina de estado de
SAIDAseria complexidade sem necessidade presente.
Linhagem de trabalho: .ia/010-adota-mensageria-*.