Article #2 – Eureka, registre de services au coeur de l’architecture
Maintenant que nous sommes familiers avec l’application qui servira de preuve pour l’architecture, nous allons commencer à implémenter une application qui va nous permettre de faire communiquer nos services entre eux sans qu’ils aient besoin de se connaître directement, grâce à Eureka.
Rappel : Cet article s’inscrit dans une série d’articles formant un retour d’expérience sur la création d’une architecture orientée microservices.
- Article #1 – Contexte et définition de l’application
- Article #2 – Eureka, registre de services au coeur de l’architecture
- Article #3 – Zuul, gatekeeper et filter loader depuis Cassandra
- Article #4 – Hystrix, son dashboard et la stack ELK
- Article #5 – Quelques éléments de conclusion
Eureka, mais pourquoi faire ?
Eureka est une application permettant la localisation d’instances de services. Elle se caractérise par une partie serveur et une partie cliente. La communication entre les parties se fait via les API Web exposées par le composant serveur. Vous pouvez retrouver la documentation complète sur le wiki du dépôt Git d’Eureka. Ces services doivent être créés en tant que clients Eureka, ayant pour objectif de se connecter et s’enregistrer sur un serveur Eureka. De ce fait, les clients vont pouvoir s’enregistrer auprès du serveur et périodiquement donner des signes de vie. Le service Eureka (composant serveur) va pouvoir conserver les informations de localisation desdits clients afin de les mettre à disposition aux autres services (service registry).
Mise en place du modèle client/serveur
Il convient donc de mettre en place un serveur Eureka et de transformer les 3 services de notre application en clients Eureka. Fort heureusement, Spring Cloud et son projet supportant la plupart des applications de Netflix, ont intégré Eureka dans leur solution, créant ainsi deux annotations : @EnableEurekaServer
et @EnableEurekaClient
Ces deux annotations font partie du module spring-cloud-starter-eureka-server
qu’il faut intégrer aux deux projets Spring Boot. Nous avons choisi d’utiliser Maven pour ce faire, et nous utilisons donc un fichier pom.xml
pour chaque projet.
Reprenons le main
de notre exemple de service pour le compléter :
@EnableAutoConfiguration
@SpringBootApplication
@EnableEurekaClient
public class DetailsApplication {
public static void main(String[] args) {
SpringApplication.run(DetailsApplication.class, args);
}
}
DetailsApplication.java
Il s’agit désormais de mettre en place le serveur Eureka afin de recevoir les informations des différents services, et de se placer au centre de l’architecture. Voici le main
du serveur :
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class, args);
}
}
EurekaServer.java
Et voici un extrait des dépendances utilisées dans les deux services (le serveur et le client) :
<parent>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-parent</artifactid>
<version>1.2.2.RELEASE</version>
</parent>
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-eureka-server</artifactid>
<version>1.0.0.RELEASE</version>
</dependency>
pom.xml
Pour l’instant, il n’y a rien de compliqué concernant la mise en place de cette relation client/serveur. Aussi, notre exemple de service et le serveur Eureka ne peuvent pas encore communiquer, car notre service ne sait pas où se trouve le serveur Eureka. Il faut donc lui indiquer la localisation du serveur, et aussi configurer le serveur.
La configuration de ces deux applications se fait en utilisant un fichier YAML. On y configure notamment Eureka ainsi que le nom de l’application afin de l’utiliser plus tard comme identifiant de service :
spring:
application:
name: details-service
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:8761/eureka/
instance:
leaseRenewalIntervalInSeconds: 10
metadataMap:
instanceId: ${spring.application.name}:${spring.application.instance_id:${server.port}:${random.value}}
Service's application.yml
Faisons désormais la même chose pour le serveur Eureka :
spring:
application:
name: EurekaServer
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
Eureka's application.yml
L’ensemble de ces paramètres va permettre au serveur Eureka de se lancer, et à notre client Eureka de se connecter au registre des services et d’y inscrire ses informations. Pour vérifier que notre service est bien connecté à Eureka, il suffit d’aller regarder l’IHM créée par Spring à l’adresse http://127.0.0.1:8761. En voici un extrait :
Pour le service Product, les choses se compliquent un peu plus. Rappelez-vous : Product se sert de Details et Pricing pour pouvoir charger les données. La requête arrivera donc d’abord dans le service Product qui, lui, va dispatcher les requêtes sur les autres services concernés (Details et/ou Pricing) en fonction des paramètres de cette requête. Product doit donc pouvoir transmettre cette requête en fonction des données enregistrées par Eureka.
Spring intervient afin de pouvoir récupérer ces informations. Tout d’abord, l’objet RestTemplate
est utilisé ici pour envoyer les requêtes aux autres services et nous le récupérons depuis le contexte Spring avec l’annotation @Autowired
. Etant donné que nous ne connaissons pas la localisation physique des services, nous allons utiliser un autre objet tiré du contexte Spring : DiscoveryClient
.
Voici un exemple de méthode permettant à Product de récupérer des informations depuis le service Details :
@Autowired
private RestTemplate rt;
@Autowired
private DiscoveryClient dc;
...
String url = dc.getNextServerFromEureka("details-service", false).getHomePageUrl();
return rt.getForObject(url, DetailsDTO.class);
ProductController.java
La méthode getNextServerFromEureka()
permet d’interroger Eureka sur la localisation du service, ici “details-service” (NB : le nom que nous lui avons donné grâce à l’attribut spring.application.name
), et de récupérer son URL. Par la suite, nous utilisons RestTemplate
et sa méthode getForObject()
pour récupérer une liste d’objets DetailsDTO (que nous verrons plus tard).
En somme, Eureka, en combinaison avec Spring, permet de fournir l’adresse des services qui se sont connectés au serveur. Cela renforce donc ce principe d’indépendance et de faible couplage entre les services. Aussi, en nous permettant l’accès au DiscoveryClient
, nous verrons qu’il sera possible de modifier la stratégie de load-balancing, en utilisant Ribbon (un IPC réalisé par Netflix), en répartissant la charge en fonction des instances d’un même service, par défaut.
Dans le prochain article, nous allons nous pencher sur l’utilisation d’Apache Cassandra afin de pouvoir récupérer les informations qui permettent de peupler notre service Product. Puis nous parlerons aussi de Zuul, le point d’entrée de notre architecture, et surtout sa capacité à faire du “dynamic filtering“.