Pular para conteúdo

Java — app simples / CLI / worker

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-06-19

O arquétipo Java puro (sem framework) — uma ferramenta, um job, um cliente de API de linha de comando. Para servidor/API a stack é Micronaut (Stack da casa); aqui está o como de um app Java que não é servidor.

Organização do código

package-by-feature (default da casa, §Stack): cada funcionalidade é um pacote de topo que carrega suas camadas; o compartilhado em comum/core. Anti-padrão a evitar: feature-first ingênuo vira "core monolítico + features anêmicas" — a regra é pipeline genérico no kernel + uma estratégia (interface) para o que varia. Base package: br.com.xadm.<cliente>.<app>.

Build — Gradle (Kotlin DSL)

  • Gradle wrapper versionado (./gradlew, nunca Maven); Java via SDKMAN.
  • Pin de dependências; gate único ./gradlew check (compila + checkstyle + testes). No agente/local, ./gradlew test --quiet --console=plain (saída enxuta, sem progresso — higiene de saída).
  • Checkstyle: copie checkstyle.xml
  • suppressions.xml do central para config/checkstyle/fonte única, não copie de outro app. Amarre no check:
plugins { checkstyle }
checkstyle { toolVersion = "10.x"; configDirectory.set(file("config/checkstyle")) }
  • Nome do jar: <app>-<versão>.jar (shadow/fat jar quando distribuído). A versão vem do project.version, nunca um literal — ver versionamento.

Logging — Logback por arquétipo

A regra STDOUT = dados/contrato; STDERR + arquivo = logs é só do CLI/worker cujo stdout é consumido por outro processo: ali um logback.xml que loga no console (stdout) corrompe a saída. Servidor é o oposto: loga em stdout (o Docker/Coolify captura) — está certo, e o /health/contrato é HTTP, não o stdout. Tenha logback.xml por arquétipo: servidor → stdout; CLI/worker → stderr + arquivo. Não copie o logback de um arquétipo para o outro.

Tipos

BigDecimal para valores monetários/fiscais — nunca double/float.

CLI (picocli)

  • picocli para parsing; o --version lê de um IVersionProvider que carrega a versão do manifesto (project.version), nunca @Command(version = "...") literal (mente e drifta — ver versionamento).
  • Exceção → exit code: 0 ok · 1 parcial · 2 config/auth · 3 externo · 4 ocupado/lock (execução concorrente serializada por lock; nada feito; seguro repetir — o agendador trata como "tente depois", não como erro). Esta lista é o contrato canônico de exit code da casa: o app não inventa código — desfecho novo entra por feedback §6. O app documenta seu comportamento de saída no runbook de operacao/, referenciando esta lista.
  • Sem flags ⇒ valida a config + imprime o help (não falha calado).
  • Log de observabilidade por requisição (envio → resposta → processado); segredo nunca logado.

Testes

  • JUnit 5. Cliente de API REST: piso = unit (normalização/mapeamento) + integração com mock HTTP (WireMock) sobre fixtures anonimizadas.
  • Convenção de fixtures (golden-master datada, receita de captura, anonimização) e Testcontainers: Testes e fixtures (Micronaut) — vale para app Java simples também.
  • Gate e níveis de teste: CI, gates e testes.
  • Não desabilite teste por OS (@DisabledOnOs(OS.WINDOWS)): ele fica invisível ao pré-flight do release (que roda no Windows), passa verde no check local e só falha na CI Linux, pós-tag. Prefira cross-OS; se for genuinamente Linux-only, rode o gate no container ci-java ou confirme a CI verde antes de taggar — gate cego pré-flight×CI.