Ao desenvolver aplicações com Spring Boot e Spring Cloud, é comum encontrar cenários onde um FeignClient é invocado durante a fase de inicialização de um bean, especificamente em métodos anotados com @PostConstruct. No entanto, pode surgir um problema em que as configurações personalizadas do Hystrix para o FeignClient não são aplicadas, levando a uma ativação prematura do mecanismo de fallback, geralmente devido a tempos limite (timeouts) padrão. Este artigo explora essa situação, as causas subjacentes e uma solução eficaz.
Contexto e Problema
O problema geralmente se manifesta em ambientes Spring Boot (ex: 2.2.x) utilizando Spring Cloud OpenFeign (ex: 2.1.x) com Hystrix habilitado. Imagine um serviço Spring (anotado com @Service) que precisa carregar informações críticas durante sua inicialização. Para isso, ele injeta um @FeignClient e o utiliza dentro de um método @PostConstruct. O comportamento observado é que a chamada ao FeignClient falha e o Hystrix entra em seu modo de Fallback imediatamente, apesar de configurações de timeout mais longas terem sido definidas no arquivo application.yml. Isso indica que as configurações do Hystrix não foram carregadas ou reconhecidas a tempo para a primeira chamada.
Configuração Exemplo
Considere a seguinte configuração no application.yml, onde tentamos definir um tempo limite de 8.5 segundos para o Hystrix e tempos limites de conexão/leitura para o Feign:
configuracao:
hystrix:
poolDeThreads:
padrao:
tamanhoMinimo: 30
tamanhoMaximo: 100
comando:
padrao:
execucao:
isolamento:
thread:
timeoutEmMilissegundos: 8500 # Tempo limite personalizado
feign:
habilitarHystrix: true
clienteHttp:
habilitar: true
cliente:
configuracoes:
geral: # Configuração geral para todos os clientes Feign
tempoConexao: 3500
tempoLeitura: 5500
Componentes Afetados
Interface Feign Client
Uma interface de cliente Feign para interagir com um serviço de configuração remoto:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "dadosConfigService", path = "api/configuracao", url = "${remote.service.host}", decode404 = true, fallback = DadosConfigFallback.class)
public interface DadosConfigClient {
@PostMapping("/parametros/buscarPorGrupoEChave")
String buscarParametro(@RequestParam("grupo") String grupo, @RequestParam("chave") String chave);
}
Implementação do Serviço
Um serviço que tenta carregar chaves de segurança usando o DadosConfigClient durante sua inicialização:
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@Slf4j
@Service
public class SecurityService {
private final DadosConfigClient dadosConfigClient;
private static String CHAVE_PUBLICA_ATUAL = null;
private static String CHAVE_PRIVADA_ATUAL = null;
private static final String GRUPO_CHAVES_APP = "app.keys";
public SecurityService(DadosConfigClient dadosConfigClient) {
this.dadosConfigClient = dadosConfigClient;
}
@PostConstruct
public void carregarChavesSeguranca() {
CHAVE_PUBLICA_ATUAL = recuperarChavePublica();
CHAVE_PRIVADA_ATUAL = recuperarChavePrivada();
}
public String recuperarChavePublica() {
String valor = dadosConfigClient.buscarParametro(GRUPO_CHAVES_APP, "publica");
if (valor == null || valor.trim().isEmpty()) {
log.error("Falha ao obter a Chave Pública do serviço de configuração.");
}
return valor;
}
public String recuperarChavePrivada() {
String valor = dadosConfigClient.buscarParametro(GRUPO_CHAVES_APP, "privada");
if (valor == null || valor.trim().isEmpty()) {
log.error("Falha ao obter a Chave Privada do serviço de configuração.");
}
return valor;
}
}
Implementação do Fallback
A classe de fallback que é ativada quando o Hystrix intercepta uma falha (ou um timeout):
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class DadosConfigFallback implements DadosConfigClient {
@Override
public String buscarParametro(String grupo, String chave) {
log.error("Fallback ativado: Não foi possível buscar o parâmetro. Grupo: {}, Chave: {}", grupo, chave);
return null;
}
}
Análise da Causa Raiz
A raiz do problema reside na ordem de inicialização dos beans do Spring e na forma como o Hystrix carrega suas configurações. Quando um bean de serviço (como SecurityService) é criado, o método @PostConstruct é invocado logo após a injeção de dependências. Se, neste momento, o FeignClient for chamado pela primeira vez, o HystrixCommand associado a ele é inicializado. Ocorre que as configurações do Hystrix, definidas no application.yml, são carregadas por um bean específico, ConfigurableEnvironmentConfiguration, parte da ArchaiusAutoConfiguration. Há uma janela de tempo em que este bean pode ainda não ter completado seu carregamento e registro das propriedades no ambiente de configuração do Hystrix/Archaius no momento da primeira invocação do FeignClient. Consequentemente, o Hystrix utiliza suas configurações padrão (geralmente um timeout de 1 segundo), ignorando as configurações personalizadas definidas no arquivo de propriedades.
Solução
Para garantir que ConfigurableEnvironmentConfiguration seja inicializado e que as configurações do Hystrix estejam disponíveis antes da invocação do FeignClient em @PostConstruct, podemos forçar a ordem de inicialização dos beans usando a anotação @DependsOn. Esta anotação indica que o bean atual depende de outro bean especificado, garantindo que o bean dependente seja criado e totalmente inicializado antes. O nome do bean a depender é "configurableEnvironmentConfiguration".
Aplicação no Serviço Específico
Você pode aplicar @DependsOn diretamente ao seu serviço que está usando o FeignClient em @PostConstruct:
import org.springframework.context.annotation.DependsOn;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@DependsOn("configurableEnvironmentConfiguration") // Garante que as configurações Hystrix sejam carregadas
@Slf4j
@Service
public class SecurityService {
// ... (restante do código do serviço)
}
Aplicação Global (na classe principal da aplicação)
Para garantir que o ConfigurableEnvironmentConfiguration seja inicializado muito cedo no ciclo de vida da aplicação, pode-se aplicar a anotação à classe principal do Spring Boot. Esta abordagem é útil se múltiplos serviços ou componentes sofrem do mesmo problema:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.DependsOn;
@DependsOn("configurableEnvironmentConfiguration") // Aplicação global
@SpringBootApplication
public class MinhaAppPrincipal {
public static void main(String[] args) {
SpringApplication.run(MinhaAppPrincipal.class, args);
}
}
Conclusão
O HystrixCommand é geralmente inicializado na primeira chamada ao FeignClient. É crucial que as propriedades de configuração do Hystrix, que são carregadas pelo bean ConfigurableEnvironmentConfiguration, já estejam disponíveis no ambiente do Archaius/Hystrix antes que essa primeira invocação ocorra. A anotação @DependsOn fornece um mecanismo eficaz para resolver problemas de ordenação de inicialização de beans, garantindo que as configurações de Hystrix sejam devidamente carregadas e aplicadas a tempo, evitando ativações prematuras do fallback e comportamentos inesperados.