No Spring, configuração importa mais do que parece
Muita equipe trata configuração como moldura.
O código "de verdade" estaria em @Service, @Repository, @Controller e nos casos de uso.
O resto pareceria só bootstrap: package scan, profile, property, classe @Configuration, enable daqui, disable dali.
No Spring, esse raciocínio cobra caro.
Configuração não é acabamento.
Ela define o que o container conhece, o que ele intercepta e até qual aplicação está rodando de fato.
O primeiro erro é achar que a lógica mora só nas classes
No Spring, comportamento útil quase nunca nasce da classe isolada.
Ele nasce da classe mais a forma como ela entrou no container.
Esse ponto conversa diretamente com os posts sobre Bean e IoC: não basta um objeto existir no projeto. O Spring precisa saber que ele existe e em que condições deve gerenciá-lo.
Quando a equipe trata configuração como detalhe, começa a presumir que a anotação na classe basta.
Não basta.
Configuração decide o que o Spring enxerga
Uma classe anotada com @Service não ganha poderes por presença textual.
Ela precisa estar dentro do universo que a configuração colocou sob gestão.
Veja este caso:
@SpringBootApplication
@ComponentScan(basePackages = "com.acme.app")
public class ApiApplication {
}
package com.acme.app.pedido;
@Service
public class PedidoService {
}
package com.acme.integracao.email;
@Service
public class EmailService {
}
À primeira vista, tudo parece anotado corretamente.
Mas EmailService ficou fora do ComponentScan.
Para o projeto, a classe existe.
Para o Spring, não.
E quando isso acontece, o problema real não é só "faltou bean".
É o desenho da aplicação dizendo uma coisa e a configuração permitindo outra.
A mesma lógica vale para @Bean em classes @Configuration, imports de módulos, auto-configuração excluída e profiles que ativam ou removem partes inteiras da aplicação.
Configuração não descreve decoração.
Configuração define fronteira de existência.
Configuração também decide quais anotações funcionam
Outro erro comum é olhar para uma anotação e presumir que o comportamento está garantido.
No Spring, isso quase nunca depende só da anotação no método.
Depende da infraestrutura que a configuração ativou.
@Async é um exemplo didático:
@Service
public class NotificacaoService {
@Async
public void enviarEmailBoasVindas(Long clienteId) {
// envio demorado
}
}
Muita gente lê esse código e já enxerga execução assíncrona.
Mas, sem a configuração que habilita esse mecanismo, a anotação não produz o efeito esperado:
@Configuration
@EnableAsync
public class AsyncConfig {
}
Sem @EnableAsync, não existe a infraestrutura responsável por criar o comportamento assíncrono via proxy.
O código continua bonito.
A expectativa do time continua alta.
Mas a semântica operacional não apareceu.
Esse raciocínio vale em vários pontos do ecossistema:
- transação;
- cache;
- agendamento;
- eventos;
- segurança;
- binding de propriedades.
No Spring, a anotação visível quase sempre é só a ponta do contrato.
A outra ponta mora na configuração.
Quando você não escolhe, o default escolhe por você
Tem um erro ainda mais traiçoeiro.
A equipe não olha para determinada configuração e conclui:
"isso nem entrou em decisão."
Entrou.
Default também é decisão em execução.
Se um profile ativo troca implementações, se uma property muda timeout, se uma auto-configuração registra um bean porque outro não existe, então a aplicação final depende dessas regras mesmo quando ninguém parou para discutir isso explicitamente.
Um exemplo simples com @Profile mostra como o código pode ser o mesmo e a aplicação mudar bastante:
@Configuration
public class GatewayConfig {
@Bean
@Profile("prod")
public GatewayPagamento gatewayPagamentoReal() {
return new GatewayPagamentoHttp();
}
@Bean
@Profile("dev")
public GatewayPagamento gatewayPagamentoFake() {
return new GatewayPagamentoFake();
}
}
O serviço consumidor pode continuar idêntico.
Mas o comportamento real do sistema muda conforme o profile ativo.
Ou seja: a diferença entre ambiente local, homologação e produção não está só nos dados.
Está no conjunto de objetos e regras que a configuração permitiu subir.
Quando o time subestima isso, começa a depurar "mistérios do framework" que, na verdade, são consequências diretas do setup.
O efeito colateral é ler o sistema errado
É por isso que bug de configuração costuma ser tão caro.
Ele não aparece como erro de sintaxe.
Ele aparece como interpretação errada do sistema.
O desenvolvedor lê uma anotação e imagina um comportamento.
A aplicação sobe com outro.
O time vê uma classe e assume que ela virou Bean.
O container nem chegou perto dela.
Alguém supõe que determinada implementação está ativa.
O profile carregou outra.
Nesse ponto, o problema não é só técnico.
É cognitivo.
A configuração errada faz o código mentir para quem está lendo.
E isso se conecta com o post sobre @Transactional: no Spring, comportamento importante depende da fronteira certa, não apenas da anotação escrita.
O ponto que vale fixar
No Spring, configuração não é bastidor neutro.
Ela define o que existe, o que funciona e em que contexto funciona.
Se o time revisa regra de negócio com atenção, mas trata application.yml, @Configuration, @Profile, @ComponentScan e enable annotations como detalhe, está auditando metade da aplicação.
Código de negócio diz o que o sistema quer fazer.
Configuração decide se o Spring vai mesmo fazer aquilo do jeito que você imaginou.
