Pular para conteúdo

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 WebhookController chama MensageriaService.registrarEntrada(tipo="poke", correlationId, payload, resumo) síncrono no thread do request, antes de disparar o sweep. Grava uma MensagemM2m direcao=ENTRADA/status=RECEBIDO. Falha do registro propaga (poke pode virar 5xx): o banco é compartilhado e o sweep também depende dele, então um 202 com o banco caído seria mentira. O 202 e 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 o Authorization/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 produz SAIDA, os botões de reenviar/cancelar (que a view só mostra em linhas SAIDA) não aparecem.
  • Tabela mensagem_m2m é do INTEGRADOR — o tradutor NÃO a cria. No db_thoms compartilhado a tabela tem um dono só, e é o integrador (remetente M2M com outbox/relay). O tradutor consome (grava ENTRADA via registrarEntrada), sem migração de produção. Em teste, sem integrador, uma migração test-only (db/migration-test/V6__mensagem_m2m.sql, ligada só no application-test.yml) cria a tabela. Deploy coordenado: o tradutor-mensageria só sobe junto/depois do integrador-mensageria (release sincronizado dos repos) — senão o registrarEntrada grava numa tabela inexistente.
  • Config (application.yml, bloco mensageria:) — 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 @Scheduled diário LimpezaMensagens que apaga linhas [ENVIADO, RECEBIDO] mais velhas que mensageria.retencao-dias (default 30). Como RECEBIDO é o status do poke, no default a auditoria sumiria em 30 dias — o oposto do valor da feature. Fixamos retencao-dias: 90 (alinha webstorm.cleanup.retencao-dias do 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 o LimpezaMensagens — este roda diário independentemente, governado por retencao-dias.
  • Gate: quem manda é a rule da lib. A MensageriaAdminRule da lib tem getOrder() = -100 e precede a ViewSecurityRule do thoms (0): decide /admin/mensageria sozinha. Exige o atributo email do principal (populado pelo SessionAuthenticationFetcher do xadm-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 da ViewSecurityRule).
  • Tabela compartilhada com o integrador. mensagem_m2m (sem o prefixo webstorm_ecom_* — é infra da lib) é a mesma que o integrador cria/usa no db_thoms: o tradutor grava as linhas ENTRADA, o integrador as SAIDA do outbox. Por isso o tradutor não a migra em produção — dois CREATE TABLE no mesmo banco colidiriam (o 2º deploy quebraria com "table already exists"). Coordenação de retenção: a LimpezaMensagens da 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 as SAIDA do integrador.
  • Bug corrigido no thoms — model imutável. A tela da lib devolve o model como Map.of(...) imutável. O GlobalViewModel (o ViewModelProcessor que injeta currentUser/csrf/marca em toda view) fazia model.put direto → UnsupportedOperationException (500 no render, quebraria a tela em prod). Passou a enriquecer uma cópia mutável e re-injetar via setModel — 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/erp na WebStorm (3º-parte externa, não par M2M da casa); sem enfileirar, SPI EnviadorMensagem nem 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-mensageria está em 0.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 SAIDA seria complexidade sem necessidade presente.

Linhagem de trabalho: .ia/010-adota-mensageria-*.