Estrutura de código — package-by-feature + core¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-10
O código é organizado por feature, sobre uma fundação de dados (core) e um kernel
(comum). O grafo de pacotes é um DAG (guarda ArchUnit no gate).
| Pacote | Papel |
|---|---|
core |
Fundação de dados: entidades (@MappedEntity), repositórios, enums (ETipo/EResultado) + conversores, ParsedXadmPayload, eventos de domínio (NotaAplicadaEvent). Não depende de nada da app. |
comum |
Kernel: config, policy de auth per-app (ViewSecurityRule, ApiBearerSecurityRule, whitelist do AuthSupport, StaticBearerTokenValidator local via @Replaces), exceção/RFC-7807, observabilidade, health (HealthController + ApiHealthController + auditoria ApiHealthRequestRecorder), e os serviços compartilhados por mais de uma feature (XadmResponse, PropriedadesNomeCurtoService). A infra transversal de auth (sessão JWT, Firebase, settings, SessionAuthenticationFetcher) vem da lib da casa xadm-seguranca (br.com.xadm.comum.seguranca, constituição, Engenharia) — não é mais recopiada aqui. Depende só de core. |
ingest |
Ingestão XADM (feature-motor): XadmController (/api/v1/xadm)/XadmService/XadmApplyService/parser + EscritaPorChaveNatural + RequestCleanupJob. |
powersync |
Controllers do PowerSync (reset, lastchange). |
push |
Push FCM: PushContextBuilder, PushNotificationService, FcmPushService, e o NotaAplicadaPushListener. |
export |
Exportação de tabelas (XLS). |
heartbeat |
Heartbeat do integrador-client (feature-motor): HeartbeatController (/api/v1/heartbeat) valida o corpo, carimba o CLIENTE_ID e repassa ao central-backend pelo CentralHeartbeatClient (cliente HTTP declarativo). Monta as próprias respostas de erro — ver erros. Não toca banco. |
api |
Camada edge (REST CRUD), ACIMA das features-motor: os 13 controllers CRUD por-entidade /api/v1/*. Pode depender das features (usa o EscritaPorChaveNatural de ingest) — não é feature-motor (decisão 0012). |
views |
Camada edge (apresentação server-render JTE, decisão 0025), ACIMA das features-motor: os *ViewController, IndexController, SwaggerController, DebugViewController. Pode orquestrar features (ex. o DebugViewController dispara push e reparo de coluna) — não é feature-motor. |
Camadas¶
core → comum → features-motor (ingest · powersync · push · export · heartbeat) → edges (api · views)
core é a fundação de dados; comum é o kernel; as features-motor constroem sobre os dois e
são independentes entre si; as camadas edge (api = REST CRUD, views = UI) ficam acima e
podem depender das features (decisão 0012).
Escrita por chave natural (upsert + soft-delete)¶
A regra de escrita das entidades de negócio — upsert com revive de soft-delete e soft-delete
por chave natural — tem um ponto único: o helper EscritaPorChaveNatural (ingest) sobre a
interface SoftDeletable (core, implementada pelas 22 entidades de escrita). Tanto o
XadmApplyService (aplica o payload XADM vindo do ERP) quanto os controllers CRUD /api/v1/*
delegam a ele — cada call-site só fornece o finder (busca por chave natural) e a cópia de
campos. Antes, esse esqueleto se repetia ~13× em cada lugar e divergia (ex.: a NotaCompl não
revivia no CRUD — corrigido ao unificar).
Exceção: Estoque, Propriedades e Veiculos mantêm lógica própria de nome-curto (campo
definido pelo mobile via PowerSync) nos seus controllers, fora do helper — de propósito, porque
essa parte não é o esqueleto genérico.
Registry de handler por tabela (ingest)¶
A aplicação do payload XADM (XadmApplyService) é dirigida por um registry de handlers, não por
código carimbado por tabela. Cada tabela do
espelho é um XadmTableHandler descoberto por DI; o XadmApplyService virou um driver puro
que só itera o XadmTableRegistry no PUT/DEL — não conhece as tabelas.
XadmTableHandler<E>— contrato de uma tabela:arrayKey(),listaDe(payload),ordemPut()/ordemDelete(),upsert/softDelete,describe(contexto do erro).AbstractXadmTableHandler<E>— base que delega upsert/soft-delete aoEscritaPorChaveNatural; o handler simples só declararepo(),buscar()(finder) ecopiaCampos(). Guarda de chave nula → sobrescrevechavePresente().- Regra própria (dual-key X-Adm × PIED:
propriedades,estoque,contratos,itensped; defaults/timestamps:mdf,notacompl,fones, grupo MDF-e) → o handler sobrescreveupsert/softDeleteinteiros, preservando o merge de domínio (mão única / write-back). - Ordem preservada:
ordemPut()eordemDelete()reproduzem as duas ordens FK-safe do driver antigo (a ordem do DEL não é o reverso do PUT — são ordens distintas). É o guardrail junto aoReentrantLockdoXadmService(anti-deadlock, incidente 2026-04-24). XadmTableRegistrytambém derivavazio(payload)(guarda no-op doXadmService) ebuildCounts(payload)(resumo de log) da lista de handlers — sem enumeração manual das 22 tabelas.
Tabela nova = 1 handler (mais o campo/parse no ParsedXadmPayload/parser, que guardam listas
tipadas): o driver, o isEmpty/buildCounts e a ordem não mudam. Coberto pelo teste de aceite
XadmTableRegistryTest (registra um handler fictício e prova que o driver o aplica sem editar o
XadmApplyService).
Fronteiras guardadas (teste de arquitetura no gate)¶
coreé puro — não depende decomumnem de features.comumdepende só decore— o kernel não conhece features.- features-motor são independentes —
ingest/powersync/push/export/heartbeatnão dependem umas das outras; o que é compartilhado por mais de uma sobe paracomum/core. (viewsfica de fora: é a camada de composição acima delas e pode orquestrá-las.) - sem ciclos entre pacotes.
O ciclo que a decomposição ingênua teria (ingest↔push, pois a nota aplicada dispara push) foi
quebrado por evento de domínio: o XadmService publica um NotaAplicadaEvent (em core) e o
NotaAplicadaPushListener (em push) reage — ingest não conhece push. Ver
push por evento e a decisão 0011.
Testes espelham as features¶
A árvore de testes (src/test/java/br/com/xadm) segue os mesmos pacotes do main — o teste de
uma classe vive no pacote da feature que ela exercita (core, comum, ingest, views,
powersync, push, export). Além desses, dois pacotes de teste:
arch— a guarda de arquitetura (ArchitectureTest), que roda no gate.support— infraestrutura de teste (helpers comoTestTransactionOperationse oCorsPreflightProbeController), não casos de teste.
Testes de integração cross-cutting ficam no pacote da feature que exercitam (ex. auth/CORS/health →
comum; rendering de tela → views) — não há um pacote integration por camada.