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.