Tous les développeurs connaissent la galaxie Spring, et notamment Boot qui permet de simplifier le démarrage et le développement de nouvelles applications Spring. En sondant un peu les collègues on se rend vite compte qu’un autre projet, tout aussi intéressant que Boot, est malheureusement beaucoup moins bien connu : Spring Cloud.

Il met a disposition des outils permettant de construire rapidement des systèmes distribués, et s’intègre parfaitement dans le développement d’architectures micro-services (gestion des configuration, service discovery, routing et micro-proxy, …).

Cette suite d’articles vise à décrire les principaux modules de Spring Cloud. Elle s’appuiera sur un exemple complet : la mise en place d’un petit ensemble de micro-services, avec en prime la découverte de services, du routing intelligent, de la gestion de configurations externalisées. Ce projet sera repris et enrichi dans chaque partie.

Description du projet et vue globale de l’architecture

Le projet

L’idée est d’exposer trois APIs, permettant une gestion très simpliste de la publication de news.

Le modèle décrit brièvement les entités manipulées (et du même coup les trois ressources qui seront exposées) :

Les trois APIs seront appelables indépendamment, mais aussi conjointement. Par exemple, lors d’un appel a l’API exposant les news, cette dernière pourra a son tour solliciter l’API « Authors » afin de renvoyer, en plus du détail d’une news, les informations relatives à l’utilisateur l’ayant rédigé.

L’architecture

Le schéma suivant illustre le fonctionnement du projet qui sera construit dans cette suite d’articles.

1. Au démarrage, les nouveaux micro-services vont consulter le service de gestion des configurations. Celui ci est interfacé avec un repo Git sur lequel se trouve les fichiers de configurations. Une fois récupéré, le service de gestion des configurations renvoie le paramétrage aux micro-services.

2. Après avoir récupéré le paramétrage, les micro-services vont se référencer auprès du service discovery. C’est ce dernier qui permet la découverte de service. Il s’agit tout bonnement d’un annuaire de service type Eureka.

3 et 4. Le service routing reçoit une requête, il va consulter le service discovery afin de trouver le micro-service concerné par cette requête.

5 et 6. Le micro-service a été trouvé, la requête est routée vers ce dernier et le résultat est envoyé a l’appelant. Le service routing sert ici de gateway (nous pourrons utiliser Zuul).


Pour ce premier article nous commencerons par la création des APIs de base (Authors, News, Categories) en Spring Boot standard. La seconde partie de l’article sera dédiée à la découverte de service.


Démarrage du projet

Dans cette partie, nous allons décrire comment créer rapidement les APIs de base. Les trois APIs ayant la même structure, on ne détaillera que la création de l’API exposant les Authors (il suffira de répéter les mêmes opérations pour les News et les Categories).

Initialisation de l’API Authors et ajout des dépendances

On commence donc par créer un nouveau projet avec notre IDE préféré, et on lui rajoute un nouveau module « api-authors ». Celui ci aura pour but de simplement exposer un CRUD sur une entité «Author» (portée également par le module).

Ce module aura la structure suivante :

On ne parlera pas du contenu des répertoires « repository » et « domain » (ceux ci concernent la persistance des données et ce n’est pas le sujet ici).

Pour information, la création d’un module est grandement simplifiée sur IntelliJ via l’outil Spring Initializr.

On ajoutera simplement les dépendances suivantes :

Les dépendances « h2 » et « spring-boot-starter-data-jpa » sont ajoutées pour la persistance des données (dans un vrai projet, on aurait pu passer par du Postgres ou MongoDb mais pour cet article H2 est largement suffisant). On importera également Lombok, qui simplifiera le code en ajoutant à la compilation les constructeurs par défaut, getters/setters et autres méthodes toString.

Implémentation de l’API Authors

Comme toute application Boot, api-authors disposera d’un launcher, implémenté par la classe ApiAuthorsApplication. Celle ci portera le code suivant :

@SpringBootApplication
public class ApiAuthorsApplication {
 public static void main(String[] args) {
  SpringApplication.run(ApiAuthorsApplication.class, args);
 }
  @Autowired
  private AuthorRepository authorRepository;
  @Bean
  public CommandLineRunner initData() {
    return args -> {
      Author author1 = new Author();
      author1.setFirstName("Tim");
      author1.setLastName("Cain");
      ...
      authorRepository.save(author1);
      authorRepository.save(author2);
      authorRepository.save(author3);
    };
  }
}

En somme elle ne fera qu’insérer des données en base via l’AuthorRepository, et démarrer l’application.

Côté configuration on ajoute au fichier application.properties l’option suivante :

server.port=8181

Celle ci va permettre de spécifier le port sur lequel sera exposée l’API.

A ce stade, lorsqu’on lance l’application, et qu’on tape http://localhost:8181/authors dans un navigateur, on obtient le résultat suivant :


Nous avons à présent nos APIs de base, et de quoi illustrer certains concepts comme la découverte de services. Passons maintenant à la mise en place du registre de services “Eureka”.

Eureka, comment ça fonctionne ?

Pour faire simple Eureka n’est ni plus ni moins qu’un annuaire de services, une application permettant de localiser les différentes instances de services.

Pour mettre en place Eureka, il faut que nos APIs soient déclarées en tant que clients Eureka. De cette façon, lors du lancement des services, ceux ci se référenceront auprès du registre de services. De son côté, Eureka va conserver les informations de localisation de chaque service afin de les mettre à disposition des autres services.

Mise en place du registre de services.

On commence par ajouter un nouveau module a notre projet.

On ajoute ensuite les dépendances suivantes dans le pom.xml :

<parent>  
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.7.RELEASE</version>  <relativePath/>
</parent>
<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

On complète la classe « main » du module comme suit :

@SpringBootApplication
@EnableEurekaServer
@EnableDiscoveryClient
public class RegistryServiceApplication {
 public static void main(String[] args) {
  SpringApplication.run(RegistryServiceApplication.class, args);
 }
}

Par rapport a une application classique, on ajoute ici deux annotations « EnableEurekaServer » et « EnableDiscoveryClient ».

Enfin on ajuste le fichier application.properties avec le contenu suivant :

server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

Le premier paramètre précise le port d’écoute, et le reste de la configuration indique simplement à cette instance de ne pas s’enregistrer avec l’instance Eureka trouvée (en somme elle ne doit pas s’auto-référencer).

Conversion des services en clients Eureka.

On ajoute la dépendance suivante aux pom.xml des APIs créées dans la partie précédente :

</dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-netflix-eureka-client</artifactId>
 <version>RELEASE</version>
</dependency>

On complète les classes « main » de ces APIs avec l’annotation EnableEurekaClient, qui spécifiera qu’il s’agit de clients Eureka. Exemple avec l’API gérant les auteurs :

@SpringBootApplication
@EnableEurekaClient
public class ApiAuthorsApplication {
 public static void main(String[] args) {
  SpringApplication.run(ApiAuthorsApplication.class, args);
 }

On ajoute ensuite les propriétés suivantes à l’application.properties de chaque API :

management.port=9191
management.security.enabled=false

Et pour terminer, on créé au même niveau que le fichier application.properties un autre fichier bootstrap.properties qui contiendra le nom avec lequel on souhaite exposer nos APIs. Exemple avec l’API permettant de gérer les utilisateurs :

spring.application.name=authors

Lancement du registre de services et des APIs.

On lance en premier le registre de service. Lorsqu’on se rend à l’adresse http://localhost:8761/ via un navigateur on accède à l’interface d’Eureka :

On s’aperçoit rapidement que pour le moment rien n’est référencé dans l’encart « Instances currently registered with Eureka ».

Lançons les APIs et rendons nous une nouvelle fois à l’adresse http://localhost:8761/.

A présent les APIs devraient s’afficher, ce qui signifie que le registre de services reconnaît bien les APIs comme des clients Eureka, mais aussi qu’il détient les informations nécessaire pour joindre telle ou telle API.


Nous avons désormais un ensemble de services référencés par Eureka. Nous avons utilisé ici la solution intégrée au package fournit par Spring Cloud Netflix : Eureka. Il s’agit d’une solution très simple a mettre en oeuvre et à administrer, mais qui présente cependant quelques défauts comme la durée de vie des enregistrements des services (avant déréférencement), la réplication parfois peu fiable de ces enregistrements auprès des autres serveurs d’un cluster, ou encore le manque d’information sur l’état de santé des API.

Une alternative intéressante aurait été d’utiliser Consul qui apporte des fonctionnalités palliant aux défauts d’Eureka. La mise en place du service discovery  avec Consul et la mise en oeuvre de ses fonctionnalités pourra faire l’objet d’un prochain article.

Dans la prochaine partie, nous verrons comment coupler notre architecture avec Zuul, qui fera office de point d’entrée et de reverse proxy au sein de l’application. Le prochain article sera également l’occasion d’aborder l’externalisation des configurations, actuellement définies en “dur” dans le projet.

D’ici là, voici quelques liens utiles pour patienter :