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.