Guia do código¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-15
Audiência: dev que abriu o repositório e quer passar os olhos e entender como o código está
organizado, antes de mergulhar classe a classe. Para o porquê das decisões, veja
decisoes/; para o desenho completo, a
Documentação Completa; para os contratos da PIED,
API da PIED.
O retrato em uma frase¶
maxsul-pied é a Fase 1 da integração PIED → X-Adm: um servidor Micronaut que só captura o raw
da PIED por dois canais (webhook + poll REST) e o persiste cru em três tabelas landing. Não
transforma nem escreve no X-Adm — isso é a Fase 2, que nasce desligada por toggle.
flowchart TB
PIED[PIED]
PIED -->|POST /webhook/pied| WH[webhook: WebhookController]
PIED -->|GET REST paginado| POLL[poll: PollJob → PollService → PiedClient]
WH --> PW[(pied_webhook)]
POLL --> PR[(pied_rest)]
POLL --> PC[(pied_cursor)]
Como o código é organizado¶
Organização package-by-feature: cada canal é um pacote de topo que carrega suas próprias camadas,
em vez de pastas globais controller//service/. O transversal vive em comum. Base:
br.com.xadm.maxsul.pied.
| Pacote | É | Responsabilidade |
|---|---|---|
webhook |
feature | recebe POST /webhook/pied, valida toggle/segredo, grava cru em pied_webhook. |
poll |
feature | job agendado que pagina a API REST da PIED e grava cada página em pied_rest (+ cursor). |
normalizado |
feature | deriva as tabelas normalizadas (pied_pedido/pied_cliente/pied_produto) do raw capturado. |
integracao |
feature | Fase 2: transform → push ao Integrador, console do operador, reconciliação. Toggle IntegracaoConfig (decisão 0001), modo PRODUCAO/STAGING (decisão 0004). |
painel |
feature | painel do colaborador Maxsul: dashboard por bucket de status + detalhe do pedido (decisão 0006). |
alerta |
feature | destinatários + envio do e-mail de erro terminal via Resend (decisão 0005). |
dados |
feature | browser read-only das tabelas pied_* + índice de operador (/homedev). |
diagnostico |
feature | captura crua (timeline/dump) e o smoke-test do GlitchTip. |
retencao |
feature | job de expurgo do raw por janela de retenção. |
comum |
base | transversal local: GlobalExceptionHandler (500 + página HTML), Json, GlobalViewModel, port ModoIntegracao. A base compartilhada da casa — HealthController, UnifiedErrorResponseProcessor (RFC 7807 de ponto único), ProblemDetail, VersaoInfo — vem da lib xadm-comum-web (br.com.xadm.comum.web; decisão 0008). |
As camadas¶
Persistência é Micronaut Data (micronaut-data-jdbc,
decisão 0022): todo repositório é @JdbcRepository. O
CRUD simples usa o CrudRepository; o que carrega regra — os upserts com ON CONFLICT, as transições da
FSM guardadas por status, as projeções — é @Query nativo numa interface Sql aninhada na classe do
repositório, que mantém a API de domínio e a transação. O corpo cru nunca é desserializado num modelo de
negócio: é gravado como jsonb como chegou. A varredura do raw que alimenta a normalização segue a mesma
forma — @Query nativo no RawCapturadoRepository, devolvendo a projeção RawLinha.
Em cada feature o controller é a borda HTTP e delega ao service, que é quem fala com o repositório
(DadosService, CapturaService, PainelService, ConsoleService, WebhookService; os destinatários
de alerta passam pelo AlertaService). Nas telas de leitura o service só repassa a consulta: a fronteira
existe para o controller não depender da persistência, não para criar regra nova.
- Webhook:
WebhookController→WebhookService→PiedWebhookRepository. O controller responde 200 imediato após o insert; erro de persistência propaga →500(a reconciliação do poll é a rede de segurança, ver api-pied.md §3.1). - Poll:
PollJob(@Scheduled) →PollService(orquestra a rodada) →PiedClient(HTTP, envelope, retry) +Paginador(paginação/parada) →PiedRestRepository/CursorRepository. Só pedidos usa o cursorpied_cursorcomo?lastUpdateAfter, avançando só em sucesso; falha de uma entidade não derruba as demais.
Travas de arquitetura¶
O gate é ./gradlew check (checkstyle + testes + cobertura + fronteiras). Cinco fronteiras são
guardadas por teste (arquitetura/FronteirasTest, ArchUnit), e não por convenção — poucas
regras, alto sinal. As quatro primeiras vêm da fábrica RegrasArquitetura da xadm-comum-teste:
| Fronteira | Por que ela importa |
|---|---|
comum não depende de nenhuma feature |
comum é a base que toda feature usa; se ela conhece uma feature, a seta se inverte e o pacote base vira refém do canal. |
| features não formam ciclo entre si | cada canal tem de poder ser lido, testado e desligado em separado — o desligar-em-separado é o kill switch da decisão 0001. |
| controller não acessa repository | o controller é a borda HTTP e delega ao service da feature, que é quem fala com a persistência; no package-by-feature os dois moram no mesmo pacote, então a fronteira é pelo tipo. A regra da lib casa GenericRepository; uma regra local casa o sufixo Repository, porque aqui o repositório é a classe que embrulha a interface Sql. |
| nada depende de controller | domínio, jobs e services não conhecem o controller nem os DTOs aninhados nele; o controller mapeia o que o service devolve. |
| persistência só por Micronaut Data | DataSource.getConnection() só na allowlist — hoje, o advisory lock de sessão do PiedClusterLock. Classe nova ali é decisão, com o motivo no comentário (decisão 0022). |
A guarda nasceu junto com a migração para a constituição 0.24.0 e pegou violação real na
primeira execução: o GlobalViewModel (em comum) injetava o IntegracaoConfig concreto,
prendendo a base ao canal de integração e fechando quatro ciclos. A correção foi inverter a seta:
comum declara o port ModoIntegracao (só o que o header precisa — rótulo do modo e se é
staging) e o IntegracaoConfig o implementa.
Limite conhecido: a guarda lê bytecode, então dependência que exista só via constante
public static final é invisível (o compilador inlina o valor). Acoplamento real — tipo em campo
ou parâmetro, injeção, chamada — é pego.
A outra fronteira que importa (não persistir credencial) também é guardada por teste:
WebhookControllerTest verifica que Authorization/X-Pied-Secret são gravados mascarados.
Por onde começar a ler¶
Application.java— entrypoint Micronaut.webhook/WebhookController.java— o caminho quente mais simples, ponta a ponta.poll/PollService.java+poll/Paginador.java— a orquestração da captura REST e a paginação.V1__landing_pied.sql(Flyway) — o schema landing que tudo alimenta.
Rodar e testar¶
Subir o app, os dois conjuntos de teste, o padrão do teste de integração e o gate local estão em Como rodar.
Referência completa¶
A árvore de classes navegável (Javadoc, interna) não é publicada neste app — o job de docs do
pipeline.yml não gera dev/api/ (a geração é opt-in). A narrativa acima é o atalho para achar por onde entrar.