Guia do código¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-14
Onde fica o quê em br.com.xadm.integracao.central. A organização é package-by-feature
(padrão da casa — engenharia/stack.md): cada feature reúne seu Controller, Service,
Repository e entidades; o transversal vive em comum. O porquê das escolhas está nas
Decisões. A referência por classe
(Javadoc) é gerada pelo CI de docs em dev/api/.
Mapa de pacotes (feature-first)¶
A → B na coluna de classes: o controller A delega ao service B.
| Pacote | Papel | Classes principais |
|---|---|---|
comum |
Transversal (nenhuma feature depende ciclicamente) | RateLimiter, config/{CentralSettings,CorsOrigins,ClockFactory}, crypto/AesCipher, email/{EmailSender,ResendEmailSender,SmtpEmailSender}, error/ApiException, jwt/{JwtService,JwtValidator}, tenant/{Cliente,ClienteRepository} |
login |
Autenticação de usuário (o core) | AuthController → AuthLoginService, PabastPasswordHasher, FirebaseTokenValidatorService, PabastSenha(+repo), AppUserAccess(+repo — acesso unificado pAbast+oauth), NewAccessRequestNotifier |
admin |
Superfície administrativa | AdminController → ClienteAdminService + PabastUsuarioAdminService, OauthFirebaseAdminController → OauthFirebaseAdminService, AdminAuthFilter, AdminApiCorsFilter |
centralapps |
Central de Apps (broker build-time) + auto-governança do cliente | SetupController → ProvisioningService, AppCatalogController → AppCatalogService + RolesCatalogSyncService, AppSelfServiceController → UsuariosDoAppService, GarageBrokerController → GaragePresignService, SetupAuthFilter, AppUserAuthFilter, AppApiCorsFilter, RolesCatalogClient, FeatureProvider, App/AppRole/AppProviderResource(+repos) |
coolify |
Client da API do Coolify (integração externa; slice compartilhado) | CoolifyClient (injectEnvs/listApplications) |
deploy |
Control-plane de deploy da frota (POST /api/ci/deploy) e painel da frota |
DeployController → DeployService + DeployStatusService, DeployFleetController → DeployFleetService, DeployReconcileService, DeployAuthFilter/DeployTokenGuard, DeployTarget/DeployAudit(+repos) |
monitoramento |
Monitor de host/apps do Coolify (Fase 1, read-only) | MonitoramentoController → MonitoramentoService, DockerStatsClient, HostMetricsReader |
integradores |
Heartbeat do integrador-client: recebimento M2M, job de silêncio e painel admin (0028) | HeartbeatController e IntegradoresAdminController → IntegradorInstanciaService, IntegradorAuthFilter/IntegradorTokenGuard, IntegradorInstancia(+repo), SilencioJob, AlertaSilencio/SentryAlertaSilencio, HorasUteis/FeriadosNacionais |
diagnostico |
Smoke-tests rodáveis em produção (/test; o do GlitchTip exige xadm_admin) |
TestController |
docs |
Poke de sync das docs (M2M do CI, sob o DeployAuthFilter) |
DocsController, DocsClient |
Deps entre features são acíclicas (ex.: admin→login/centralapps, centralapps→login/coolify,
monitoramento→coolify; nenhum volta; comum não depende de feature). integradores e diagnostico
dependem só de comum: as rotas deles sob credencial admin (/api/admin/integradores, /test/glitchtip)
são cobertas pelo AdminAuthFilter por path, sem importar admin. O CoolifyClient mora no
slice coolify (não em centralapps) desde que monitoramento também passou a consumi-lo
(decisão 0019). Ver também
decisão 0010.
Dois efeitos práticos da regra "sem ciclos", que já morderam: o helper corsHeaders mora em
comum/config/CorsOrigins, e não no filtro de admin, porque o filtro gêmeo de centralapps
precisa dele; e o AdminContato do corpo do 403 é um record local ao pacote login, em vez de
reusar o de centralapps.
Camadas dentro da feature¶
- Controller (
@Controller) é a borda HTTP: rota, Bean Validation do corpo, leitura dos atributos que o filtro pôs no request (o escopo(cliente, app)do claim, o gate de papel) e o mapeamento para os DTOs HTTP —record @Serdeableaninhados no próprio controller. Não injeta repository nemDataSource; injeta um ou dois services. - Service (
@Singleton, sufixoService) é a regra: consulta e grava pelos repositories, lançaApiException(status, code, msg)(problem+json, decisão 0016) e devolve entidade ou record próprio —PabastUsuarioAdminService.UsuarioComAcesso,AppCatalogService.AppDetalhe,UsuariosDoAppService.Usuarios—, nunca o DTO do controller. Nenhum service abre transação (@Transactional): cada chamada de repository commita sozinha. - Repository (
@JdbcRepository) é chamado por service, job (SilencioJob) ou notifier (NewAccessRequestNotifier) — nunca por controller. - Entidade (
@MappedEntity/@Embeddable) é dado: não conhece settings, container de beans, HTTP nem JDBC cru.
Fronteiras travadas¶
O ArchitectureTest (ArchUnit, roda no check) cobra as fronteiras de
Java/Micronaut, Guarda de fronteiras de arquitetura.
As cinco primeiras regras vêm da fábrica RegrasArquitetura da xadm-comum-teste; a de entidade é deste app:
| Regra | O que trava |
|---|---|
IMPORT_NAO_VAZIO |
import de zero classe falha alto, em vez de deixar as outras regras passarem vácuas |
SEM_CICLOS_ENTRE_FEATURES |
ciclo entre os pacotes de topo |
BASE_COMUM_NAO_DEPENDE_DE_FEATURES |
comum dependendo de qualquer feature |
CONTROLLER_NAO_ACESSA_REPOSITORY |
@Controller dependendo de repository (GenericRepository) ou de javax.sql |
DOMINIO_NAO_DEPENDE_DE_CONTROLLER |
qualquer classe fora do controller dependendo dele ou de um DTO aninhado nele (o código que o Micronaut gera — bean definitions, introspecções, serdes — fica fora) |
CONFIG_E_INFRA_NAO_VAZAM_PARA_O_DOMINIO |
entidade dependendo de comum.config, io.micronaut.context, io.micronaut.http ou javax.sql |
Controller e repository moram no mesmo pacote, então as regras de camada são por tipo (anotação,
herança), não por pacote. A guarda só morde com ArchUnit ≥ 1.4.1: abaixo disso o ASM não lê
bytecode do JDK 25, descarta as classes em silêncio e as regras passam vácuas, verdes — é o que o
IMPORT_NAO_VAZIO pega.
Fluxo de uma requisição¶
AuthController → limita (RateLimiter) → AuthLoginService, que consulta os repositories, confere a
identidade (PabastPasswordHasher ou FirebaseTokenValidatorService) e emite o token (JwtService).
Em /api/admin/** e no /test/glitchtip, o AdminAuthFilter roda antes do controller,
validando o JWT interno com JwtValidator (decisão 0007).
Invariantes que o código mantém¶
alg=RS256no JWKS — oJwtServiceadiciona à mão; onimbus-jose-jwtnão inclui por padrão e o PowerSync recusa sem ele (decisão 0002).PabastSenhaRepositorysó por par(cliente_id, id)— os finders herdadosfindById/update/deleteById(poridsozinho) não devem ser usados: cruzariam clientes (decisão 0008).- Algoritmo pAbast imutável —
PabastPasswordHasheré porta literal do integrador/Flutter; mudar a lógica quebra os hashes em produção. - Senha em claro nunca sai — as respostas do admin trazem os hashes pAbast
(
senha/senha_compl), que a UI admin lista; a senha em claro só entra, e oPabastUsuarioAdminServicea hasheia antes de gravar. - Migrations Flyway congeladas — as
V*já aplicadas não se editam; criar nova.