Pular para conteúdo

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 ao EscritaPorChaveNatural; o handler simples só declara repo(), buscar() (finder) e copiaCampos(). Guarda de chave nula → sobrescreve chavePresente().
  • Regra própria (dual-key X-Adm × PIED: propriedades, estoque, contratos, itensped; defaults/timestamps: mdf, notacompl, fones, grupo MDF-e) → o handler sobrescreve upsert/softDelete inteiros, preservando o merge de domínio (mão única / write-back).
  • Ordem preservada: ordemPut() e ordemDelete() 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 ao ReentrantLock do XadmService (anti-deadlock, incidente 2026-04-24).
  • XadmTableRegistry também deriva vazio(payload) (guarda no-op do XadmService) e buildCounts(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 de comum nem de features.
  • comum depende só de core — o kernel não conhece features.
  • features-motor são independentes — ingest/powersync/push/export/heartbeat não dependem umas das outras; o que é compartilhado por mais de uma sobe para comum/core. (views fica 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 como TestTransactionOperations e o CorsPreflightProbeController), 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.