Gerenciamento de Configurações e Rotas Dinâmicas no Spring Cloud com Nacos

Desafios na Arquitetura de Microsserviços

Ao decompor uma aplicação monolítica em uma arquitetura de microsserviços, surgem desafios operacionais significativos: proliferação de configurações duplicadas em cada repositório, necessidade de reinicialização constante dos serviços para atualizar parâmetros de negócio e a rigidez das rotas definidas estaticamente no gateway. O Nacos resolve essas limitações oferecendo, além do registro de serviços, um robusto centro de gerenciamento de configurações.

Centralizando Configurações Compartilhadas

Em vez de manter propriedades de conexão com banco de dados, configurações de ORM e logs em cada microsserviço, centralize-as no Nacos. Isso garante consistência e facilita a manutenção.

database:
  connection:
    driver: com.mysql.cj.jdbc.Driver
    pool: com.zaxxer.hikari.HikariDataSource
    endpoint: jdbc:mysql://${env.db.host}:${env.db.port}/${env.db.name}?rewriteBatchedStatements=true
    credentials:
      user: ${env.db.user}
      pass: ${env.db.pass}

Importando Configurações Remotas

O Spring Cloud permite importar essas configurações remotas, sobrescrevendo valores locais quando houver conflito, seguindo a regra de que configurações carregadas posteriormente têm precedência.

Dependências Necessárias

Adicione o starter do Nacos Config e o suporte para bootstrap (ou importação direta) no seu arquivo de build:

<!-- Gerenciamento de configurações via Nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- Suporte para carregamento de configurações externas -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

Configuração do Cliente

Configure o arquivo principal da aplicação para apontar para o servidor Nacos e importar os arquivos de configuração desejados:

spring:
  application:
    name: core-service
  cloud:
    nacos:
      server-addr: ${env.nacos.host}:8848
  config:
    import:
      - optional:nacos:core-service.yaml
      - optional:nacos:shared-database.yaml
      - optional:nacos:shared-orm.yaml
      - optional:nacos:shared-distributed-tx.yaml

Atualização Dinâmica de Configurações (Hot Reload)

O Nacos permite que os microsserviços recebam atualizações de configuração em tempo real, sem a necessidade de reiniciar a JVM. Existem duas abordagens principais para consumri esses dados atualizados.

Abordagem 1: Mapeamento via @ConfigurationProperties

Para conjuntos de configurações extensos ou ruetilizados em múltiplos cenários, vincular as propriedades a um objeto estruturado é a prática mais recomendada.

@Component
@ConfigurationProperties(prefix = "app.settings")
public class SystemProperties {
    private String primaryEndpoint;
    private String secondaryEndpoint;

    public String getPrimaryEndpoint() { return primaryEndpoint; }
    public void setPrimaryEndpoint(String primaryEndpoint) { this.primaryEndpoint = primaryEndpoint; }

    public String getSecondaryEndpoint() { return secondaryEndpoint; }
    public void setSecondaryEndpoint(String secondaryEndpoint) { this.secondaryEndpoint = secondaryEndpoint; }
}

Injete a classe de propriedades nos seus serviços. O Spring gerenciará a atualização automática dos campos quando o contexto for.refreshed.

@Autowired
private SystemProperties systemProperties;

Abordagem 2: Injeção via @Value com @RefreshScope

Para configurações pontuais, você pode utilizar a anotação @RefreshScope combinada com @Value.

@Service
@Transactional
@RefreshScope
public class ExternalApiService {
    
    @Value("${app.settings.primary-endpoint}")
    private String primaryUrl;

    @Value("${app.settings.secondary-endpoint}")
    private String secondaryUrl;
    
    // Lógica de negócio...
}

Implementação de Rotas Dinâmicas no Gateway

Por padrão, o Spring Cloud Gateway carrega as regras de roteamento na inicialização e as mantém em cache. Para alterar rotas em produção sem derrubar o gateway, combinamos o hot reload do Nacos com um listener de alterações que atualiza a tabela de rotas em memória.

Dependências do Gateway

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

Componente de Atualização de Rotas

Crie um componente que escuta as mudanças no Nacos e reflete essas alterações no RouteDefinitionWriter do Gateway. A utilização de injeção via construtor e coleções thread-safe melhora a robustez da implementação.

package com.example.gateway.routing;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.fastjson2.JSON;
import jakarta.annotation.PostConstruct;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;

@Component
public class NacosRouteDefinitionLocator {

    private final NacosConfigManager configManager;
    private final RouteDefinitionWriter routeWriter;
    private final ApplicationEventPublisher eventPublisher;
    private final Set<String> trackedRoutes = ConcurrentHashMap.newKeySet();

    @Value("${gateway.nacos.data-id}")
    private String configDataId;

    @Value("${gateway.nacos.group}")
    private String configGroup;

    public NacosRouteDefinitionLocator(NacosConfigManager configManager, 
                                       RouteDefinitionWriter routeWriter, 
                                       ApplicationEventPublisher eventPublisher) {
        this.configManager = configManager;
        this.routeWriter = routeWriter;
        this.eventPublisher = eventPublisher;
    }

    @PostConstruct
    public void initializeListener() throws Exception {
        String initialConfig = configManager.getConfigService()
            .getConfigAndSignListener(configDataId, configGroup, 5000L, new Listener() {
                @Override
                public Executor getExecutor() { 
                    return null; 
                }
                
                @Override
                public void receiveConfigInfo(String newConfig) {
                    refreshRoutes(newConfig);
                }
            });
        
        refreshRoutes(initialConfig);
    }

    private void refreshRoutes(String jsonPayload) {
        // Remove rotas antigas rastreadas
        trackedRoutes.forEach(id -> routeWriter.delete(Mono.just(id)).subscribe());
        trackedRoutes.clear();

        // Processa e salva as novas definições de rota
        List<RouteDefinition> definitions = JSON.parseArray(jsonPayload, RouteDefinition.class);
        if (definitions != null) {
            definitions.forEach(def -> {
                routeWriter.save(Mono.just(def)).subscribe();
                trackedRoutes.add(def.getId());
            });
        }
        
        // Publica evento para forçar o refresh do cache de rotas do Gateway
        eventPublisher.publishEvent(new RefreshRoutesEvent(this.routeWriter));
    }
}

Configuração JSON no Nacos

No console do Nacos, crie um arquivo de configuração no formato JSON contendo as definições de rota. A estrutura do JSON deve espelhar a hierarquia de objetos RouteDefinition do Spring Cloud Gateway, incluindo predicates, filters e o URI de destino, permitindo uma atualização fluida e sem interrupções no tráfego de rede.

Tags: spring-cloud Nacos spring-cloud-gateway configuration-management dynamic-routing

Publicado em 7-8 05:32