Le contexte de l'architecture microservices
Le passage des applications monolithiques aux architectures microservices a introduit de nouveaux défis complexes : découplage des services, communiccation distribuée, découverte d'instance et gestion des pannes. Spring Cloud est un écosystème de frameworks conçu pour fournir une solution standardisée et intégrée à ces problématiques, en s'appuyant sur les conventions de Spring Boot.
Les composants fondamentaux
1. Gestion de la configuration et découverte de services avec Nacos
Nacos (Alibaba) agit à la fois comme serveur de découverte de services et centre de configuration centralisé, remplaçant en grande partie les solutions plus anciennes comme Eureka et Spring Cloud Config.
Fonctionnalités clés :
- Enregistrement et Découverte : Les microservices s'enregistrent auprès de Nacos au démarrage. Les consommateurs découvrent dynamiquement les adresses des fournisseurs via le nom du service, sans codage en dur d'IP.
- Configuration dynamique : Permet de modifier les paramètres d'application (ex: pools de connexions) sans redéploiement. Les configurations sont organisées par
Data ID,GroupetNamespace(isolation des environnements). - Contrôle de santé : Surveillance active des instances enregistrées et suppression automatique des instances défaillantes de la liste de routage.
Déploiement typique en cluster (environnement de producsion) :
- Préparer une base de données MySQL partagée et y exécuter le script
nacos-mysql.sql. - Configurer
application.propertiesavec les accès à la BDD sur chaque nœud. - Lister les adresses de tous les nœuds dans le fichier
conf/cluster.conf(ex:192.168.1.101:8848). - Lancer chaque nœud avec la commande
sh startup.sh(sans le mode standalone). - Utiliser un équilibreur de charge (comme Nginx) devant le cluster Nacos pour fournir un point d'accès unique.
2. Routeur d'API avec Spring Cloud Gateway
Gateway est la solution de routage et filtrage pour les API dans l'écosystème Spring Cloud. Il remplace Zuul et offre une programmation réactive basée sur Netty et Spring WebFlux.
Concepts centraux :
- Route : Une définition qui lie un prédicat (condition de correspondance) à une URI de destination. Les requêtes correspondantes sont acheminées vers le service cible (souvent via
lb://service-namepour le load balancing client). - Prédicat (Predicate) : Détermine si une requête HTTP correspond à une route. Il peut tester le chemin (
Path), la méthode, les en-têtes (Header), les paramètres de requête (Query) ou même l'heure. - Filtre (Filter) : Intercepte les requêtes/réponses pour appliquer une logique. Les GatewayFilters s'appliquent à des routes spécifiques, tandis que les GlobalFilters s'appliquent à toutes les routes (utile pour l'authentification globale).
Exemple de configuration et de filtre global pour l'authentification :
spring:
cloud:
gateway:
routes:
- id: product_route
uri: lb://product-service
predicates:
- Path=/api/products/**
@Component
@Order(-1)
public class AuthenticationFilter implements GlobalFilter {
@Override
public Mono<void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (token == null || !isValid(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
private boolean isValid(String token) {
// Logique de validation du token
return true;
}
}
</void>
3. Clients HTTP déclaratifs avec OpenFeign
OpenFeign simplifie la création de clients HTTP en permettant de définir une interface d'API annotée. La création du proxy, la sérialisation et l'intégration avec le load balancer sont gérées automatiquement.
Avantages principaux :
- Code concis et lisible, proche de la programmation locale.
- Intégration native avec des mécanismes de résilience comme Resilience4j (successeur d'Hystrix).
- Support des codecs personnalisés (ex: passer de HttpURLConnection à OkHttp pour les performances).
Optimisation typique et définition d'un client :
// Activer et configurer le client HTTP avec pool de connexions
feign:
client:
config:
default:
loggerLevel: BASIC
httpclient:
enabled: true
max-connections: 200
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/users/{id}")
UserDto getUser(@PathVariable Long id);
}
@Component
public class UserClientFallback implements UserClient {
@Override
public UserDto getUser(Long id) {
// Logique de secours en cas d'échec
return UserDto.builder().id(id).name("Utilisateur indisponible").build();
}
}
4. Résilience avec Resilience4j
Resilience4j est la bibliothèque de référence pour implémenter des patterns de tolérance aux pannes dans les applications Spring modernes. Elle est souvent utilisée en conjonction avec Feign via des annotations Spring Cloud CircuitBreaker.
Patterns implémentés :
- Circuit Breaker : Ouvre le circuit après un certain taux d'échec, évitant ainsi d'impacter les services en aval.
- Rate Limiter : Limite le nombre de requêtes par période pour protéger un service contre la surcharge.
- Retry : Réessaie automatiquement les opérations en cas d'exceptions transitoires (ex: timeout réseau).
- Bulkhead : Isole les ressources (ex: pools de threads) pour éviter qu'une panne ne se propage.
Traiter les problèmes de CORS (Cross-Origin Resource Sharing)
Dans une architecture basée sur le Gateway, les requêtes provenant d'une origine différente (ex: frontend sur un autre port ou domaine) sont bloquées par défaut par les navigateurs. La configuration doit être faite au niveau du Gateway.
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://www.mon-frontend.com"
allowedMethods:
- GET
- POST
- PUT
- DELETE
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
Synthèse
Spring Cloud offre une collection intégrée de projets qui répondent aux besoins fondamentaux d'une architecture microservices moderne : découverte de service et configuraton (Nacos), routage (Gateway), communication inter-services (Feign) et résilience (Resilience4j). Sa force rélie dans sa cohérence avec l'écosystème Spring, permettant une adoption relativement aisée pour les développeurs familiers avec Spring Boot, tout en masquant une grande partie de la complexité inhérente aux systèmes distribués.