[REX] Architecture orientée microservices avec Netflix OSS et Spring – Article #2

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.

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 :

eureka-sample

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“.