Este guia apresenta práticas recomendadas para desenvolvimento em Java/Spring no time de T.I.
O objetivo é garantir código mais limpo, testável e sustentável, reduzindo problemas de manutenção a longo prazo.
Toda API deve conter documentação técnica atualizada no README.md do repositório.
O objetivo é facilitar o entendimento da aplicação, acelerar onboarding de novos membros, aumentar a qualidade das entregas e garantir rastreabilidade das mudanças.
- Transparência: todos têm acesso rápido ao funcionamento da API.
- Onboarding mais ágil: novos desenvolvedores entendem rapidamente o contexto do projeto.
- Facilita testes e troubleshooting: informações centralizadas evitam perda de tempo.
- Inclusão: textos claros e estruturados auxiliam pessoas com deficiência visual que utilizam leitores de tela.
| Seção | Conteúdo esperado |
|---|---|
| 📖 Contexto | Descreva o propósito da API, público-alvo e papel dentro do ecossistema. |
| 🔗 Dependências de Infra | Liste Bancos, filas, tópicos Kafka, secrets ou outros serviços consumidos. |
| 📦 Payloads de entrada e saída | Exponha exemplos de requisições e respostas em JSON ou outros formatos. |
| 🚀 Como subir localmente | Explique passo a passo como rodar a API em ambiente local (Docker, variáveis de ambiente, comandos Maven/Gradle). |
| 🧩 Endpoints principais | Liste endpoints com métodos HTTP, parâmetros e breve descrição. |
| 🔐 Segurança | Descreva autenticação, autorização, tokens, certificados etc. |
| Cite restrições de uso, quotas ou pontos críticos. | |
| 📊 Fluxograma / Mermaid | Representação visual dos principais fluxos ou interações da API usando Mermaid ou fluxograma equivalente. |
POST /pagamentos
{
"idCliente": "12345",
"valor": 100.50,
"formaPagamento": "boleto"
}flowchart LR
api e1@--> base
e1@{ animation: fast }
O uso de @Autowired (principalmente em atributos) é desencorajado.
Prefira injeção por construtor, uma abordagem mais moderna e segura.
- Imutabilidade: dependências são finais e não podem ser alteradas após a construção.
- Facilidade de teste: facilita o uso de mocks sem precisar de contexto Spring.
- Clareza: dependências explícitas no construtor.
- Ciclo de vida previsível: reduz problemas de inicialização tardia.
- Padrão moderno: a partir do Spring 4.3, se houver apenas um construtor, o
@Autowirednão é necessário.
@Component
public class PaymentService {
private final PaymentRepository repository;
// Injeta via construtor (melhor prática)
public PaymentService(PaymentRepository repository) {
this.repository = repository;
}
}- Spring Documentation – Dependency Injection
- Why Field Injection is Evil (StackOverflow)
- Baeldung – Constructor Injection in Spring
Lombok é uma biblioteca popular para reduzir boilerplate, mas traz pontos negativos significativos para manutenção e evolução do código.
A decisão de não utilizar Lombok visa manter o projeto mais simples, sustentável e inclusivo para todos os membros do time.
- Acoplamento: adiciona dependência extra que pode dificultar atualizações do Java e do Spring.
- Problemas de compilação: gera código na fase de compilação, podendo causar incompatibilidades e dificultar debugging.
- Legibilidade: oculta o código gerado, tornando difícil entender o comportamento real para novos membros do time.
- Experimental: lombok tem muitas features experimentais misturadas com as estaveis, correndo o risco de implementar algo nao confiavel em produção.
- Java Records (Java 14+) para DTOs:
public class Customer {
private final String name;
private final String email;
public Customer(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() { return name; }
public String getEmail() { return email; }
}Para manter consistência, compatibilidade e suporte técnico, todos os projetos do time devem especificar as versões obrigatórias das tecnologias principais.
| Tecnologia | Versão obrigatória | Observações |
|---|---|---|
| Java | 21 | Utilizar o JDK 21, garantindo suporte às features modernas do Java, como records, pattern matching e melhorias de performance. |
| Spring Boot | 3.x | Garantir compatibilidade com Java 21 e uso das novas APIs do Spring 3, incluindo Spring Web, Spring Data e segurança. |
Para melhorar clareza, inclusão e acessibilidade, o código-fonte, comentários e nomes de variáveis/métodos devem ser escritos em português claro.
- Inclusão e acessibilidade: pessoas com deficiência visual ou que utilizam leitores de tela conseguem entender melhor o código.
- Clareza para o time: facilita comunicação, pair programming e onboarding de novos membros.
- Manutenção simplificada: reduz ambiguidades e aumenta a legibilidade do código.
- Facilita documentação: comentários e README.md ficam consistentes e compreensíveis.
- Nomes de variáveis, métodos e classes devem ser descritivos em português.
- Comentários devem explicar o propósito do código, não apenas repetir o que ele faz.
- Evitar abreviações ou termos técnicos obscuros que possam dificultar o entendimento.
- Usar padrões consistentes no time, por exemplo:
calcularJurosSimples()ao invés decalcJuros().
// Calculadora de juros simples
public class CalculadoraDeJuros {
/**
* Calcula o valor de juros simples com base no capital, taxa e tempo.
*
* @param capital Valor inicial sobre o qual os juros serão calculados
* @param taxa Taxa de juros em percentual
* @param tempo Tempo em meses
* @return Valor dos juros simples
*/
public double calcularJurosSimples(double capital, double taxa, int tempo) {
return capital * (taxa / 100) * tempo;
}
}Tip
- Mantenha consistência entre todos os projetos do time.
- Prefira português nos nomes de classes, métodos e pacotes relacionados ao negócio.
- Comentários devem ser curtos, claros e informativos, evitando redundância.
- Se houver integração com sistemas externos, manter clareza ao mapear termos do domínio de negócio.
Embora o uso de interfaces seja uma prática comum em projetos Java, não é necessário criar interfaces para todas as classes, especialmente em aplicações simples ou de baixa complexidade.
- Simplicidade: projetos simples ficam mais fáceis de entender e manter sem abstrações desnecessárias.
- Menor complexidade: evita acoplamento excessivo e excesso de classes/métodos.
- Agilidade: desenvolvimento mais rápido e direto, sem sacrificar qualidade.
- Foco no negócio: permite que o time concentre esforços nas regras de negócio, não em padrões arquiteturais que não agregam valor.
- Se houver mais de uma implementação prevista (ex.: repositório em memória e em banco).
- Se for necessário mock para testes unitários complexos.
- Quando a aplicação tende a escalar em complexidade no futuro.
// Classe concreta suficiente para casos simples
public class CalculadoraDeJuros {
public double calcularJurosSimples(double capital, double taxa, int tempo) {
return capital * (taxa / 100) * tempo;
}
}public interface RepositorioPagamento {
void salvar(Pagamento pagamento);
}
public class RepositorioPagamentoBasePersistente implements RepositorioPagamento {
@Override
public void salvar(Pagamento pagamento) {
// lógica de persistência na base fisica
}
}
public class RepositorioPagamentoMemoria implements RepositorioPagamento {
@Override
public void salvar(Pagamento pagamento) {
// lógica de persistência em memória (teste ou protótipo)
}
}Tip
- Avaliar o tamanho e complexidade do projeto antes de criar interfaces.
- Priorizar soluções diretas e simples quando não houver necessidade de múltiplas implementações.
- Revisar interfaces periodicamente para evitar abstrações que não agregam valor real.
O padrão Hexagonal Architecture (ou Ports & Adapters) organiza sistemas de forma modular, testável e flexível, separando a lógica de negócio das dependências externas (bancos, filas, APIs externas).
- Isolamento da lógica de negócio: regras de negócio ficam independentes de frameworks, bancos ou APIs externas.
- Facilidade de testes: é simples testar o domínio sem depender de infraestrutura.
- Flexibilidade para mudanças: trocar implementações externas sem alterar o core do sistema.
- Padronização do time: facilita manutenção e onboarding.
- Crie Ports (interfaces) apenas quando houver:
- Mais de uma implementação prevista (ex.: repositório em memória e em banco).
- Necessidade de mock para testes unitários complexos.
- Potencial de crescimento em complexidade futura.
- Em aplicações simples ou protótipos, prefira classes concretas diretas, mantendo o sistema mais simples e prático.
+-------------------+
| Aplicação / UI |
+-------------------+
|
+-------------------+
| Adapters / Driver |
| (Controllers, |
| REST, Kafka) |
+-------------------+
|
+-------------------+
| Ports/API |
| (Interfaces – apenas quando necessário) |
+-------------------+
|
+-------------------+
| Core / Domain |
| (Regras de Negócio) |
+-------------------+
|
+-------------------+
| Adapters / Driven |
| (Banco, S3, etc.)|
+-------------------+
// Port – criar apenas se houver necessidade de múltiplas implementações
public interface RepositorioPagamento {
void salvar(Pagamento pagamento);
}
// Adapter – implementação concreta
public class RepositorioPagamentoDynamoDB implements RepositorioPagamento {
@Override
public void salvar(Pagamento pagamento) {
// lógica para salvar no DynamoDB
}
}
// Core / Domain
public class ServicoPagamento {
private final RepositorioPagamento repositorio;
public ServicoPagamento(RepositorioPagamento repositorio) {
this.repositorio = repositorio;
}
public void processar(Pagamento pagamento) {
// regras de negócio
repositorio.salvar(pagamento);
}
}
// Em casos simples, classe concreta pode ser usada direto sem interface
public class ServicoPagamentoSimples {
public void processar(Pagamento pagamento) {
// lógica simples sem abstração
System.out.println("Pagamento processado: " + pagamento.getId());
}
}Tip
- Evitar interfaces desnecessárias em projetos simples.
- Criar Ports apenas quando houver necessidade real de abstração ou testes complexos.
- Separar claramente domain, adapters e ports.
- Manter testes unitários do core independentes de infraestrutura.
Para manter histórico claro, consistente e rastreável, todos os commits devem seguir um padrão definido pelo time.
Isso facilita revisão de código, debugging e entendimento das mudanças.
- Histórico legível: todos entendem rapidamente o que cada commit faz.
- Facilita code review: padrões consistentes ajudam revisores a identificar mudanças relevantes.
- Integração com ferramentas: mensagens padronizadas podem ser usadas em changelogs automáticos ou CI/CD.
- Inclusão: mensagens claras auxiliam membros que utilizam leitores de tela ou tradução automática.
- Formato sugerido (Conventional Commits):
<tipo>: <descrição curta>
[descrição detalhada opcional]
[referência a issue ou ticket opcional]
- Tipos comuns de commit:
| Tipo | Uso |
|---|---|
feat |
Nova funcionalidade |
fix |
Correção de bug |
docs |
Alterações na documentação |
refactor |
Refatoração de código sem mudança de comportamento |
test |
Adição ou correção de testes |
chore |
Tarefas de manutenção (build, scripts, configs) |
- feat: adicionar validação de CPF
- fix: corrigir cálculo de juros simples
- docs: atualizar exemplos de payload
- refactor: simplificar método de processamento
O Spring Boot é um framework opinativo, ou seja, ele já fornece configurações padrão inteligentes para a maioria das bibliotecas e integrações.
Não é necessário criar manualmente diversos Beans, a menos que seja uma customização específica.
- Reduz complexidade e boilerplate.
- Mantém código mais limpo e legível.
- Aproveita configurações padrão de alto desempenho do Spring Boot.
Não é necessário criar manualmente o Bean do RedisTemplate se você estiver usando o Starter do Spring Data Redis.
Código desnecessário:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
return template;
}
}# application.properties
spring.redis.host=localhost
spring.redis.port=6379- O Spring Boot automaticamente cria e configura o RedisTemplate.
- Você pode injetar diretamente onde precisar:
@Service
public class ServicoCache {
private final RedisTemplate<String, Object> redisTemplate;
public ServicoCache(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void salvar(String chave, Object valor) {
redisTemplate.opsForValue().set(chave, valor);
}
}Tip
- Evitar criar Beans que já são fornecidos pelo Spring Boot.
- Criar Beans personalizados somente quando houver necessidade de configuração específica.
- Confiar nas convenções e starters do Spring Boot para reduzir código desnecessário.