Communication entre plusieurs micro-services sans dépendance directe entre eux.

Alex Fasciaux
Reparcar
Published in
7 min readJun 22, 2021

Une des problématiques rencontrée lors du passage en micro-services concerne la gestion des événements et plus précisément la communication entre plusieurs micro-services sans dépendance directe entre eux.

Il est important de préciser que limiter les dépendances entre les micro-services nous semble être un point très important.

Après avoir analysé beaucoup de pattern micro-service dédié à la communication entre micro-services, et en vue de la complexité et le peu d’informations misent à disposition, nous avons décidé de désigner notre propre logique pour résoudre cette problématique.

Notre réflexion s’est assez naturellement orientée vers une solution simple, lisible et principalement scalable.

Pour mettre en évidence la problématique, nous allons nous baser sur un exemple simple de modèle e-commerce et plus précisément la gestion de promotion catalogue.

Dans notre modèle d’exemple, nous allons mettre en place 2 micro-services : «catalog » et «promotion».

Avant d’aller plus loin, essayons d’expliquer rapidement à quoi vont servir réellement ces micro-services.

Micro service Catalog

Ce micro-service aura pour tâche la gestion globale de notre catalogue produit. Nous allons ici retrouver toutes les informations relatives aux produits (nom, description, prix …). La catégorisation des produits se trouve dans ce même service.

Micro service Promotion

Ce micro-service aura pour objectif de gérer les promotions sur notre catalogue. Par exemple exemple appliquer une réduction à tous les prix des produits d’une catégorie donnée.

Mise en situation

Imaginons que nous souhaitons appliquer une promotion de 20 % sur une de nos catégories de produits. Nous souhaitons donc mettre à jour les prix de tous les produits appartenant à cette catégorie.

Rappelons que notre micro-service « Catalog » n’a aucune dépendance directe avec le micro-service « promotion » et inversement.

Notre micro-service « promotion » va donc devoir notifier notre service « Catalog » qu’une règle de promotion doit être appliquée pour une catégorie donnée et qu’il faut par extension mettre à jour les prix des produits concernés.

deux questions se posent alors rapidement :

  1. Comment notre service « promotion » peut-il notifier un autre service de cette création de promotion ?
  2. Comment le service notifié exécutera-t-il ensuite le traitement voulu.

La solution que nous avons imaginée, consiste à mettre en place un système d’abonnement et d’exécution de tâches entre micro-services sous forme de hooks.

Pour mener à bien ce processus d’abonnement, nous avons développé un micro-service autonome de gestion de ces hooks, que nous avons implicitement nommé « webhook ».

Comment fonctionne ce micro-service ?

Pour bien comprendre comment fonctionne notre système de hooks, nous avons introduit 2 notions nous paraissant importante : « subscriber » et « handler »

Les subscribers représentent les abonnements, ou plus simplement, n’importe quel micro-service souhaitant s’abonner à un type d’événement en provenance d’un autre micro-service.

Un abonnement sera représenté principalement par :

  • le service souhaitant s’abonner
  • le service auquel il souhaite être abonné
  • le type d’évènement auquel il s’abonne
  • l’url (route) à exécuter lors de cet évènement

Un micro-service pourra grâce à ce système, s’abonner à un ou plusieurs autre(s) micro-service(s) et à un ou plusieurs type(s) d’événement(s).

Les handlers quant à eux représentent l’émission d’un ou plusieurs événement(s).

Un handler sera représenté principalement par :

  • le service qui émet l’événement
  • le type d’événement émit
  • la donnée utile à cet événement (et l’exécution de la tâche)

Revenons à notre exemple entre « catalog » et « promotion ».

Notre service « promotion » devra émettre un évènement lors de la création d’une nouvelle promotion. Pour ce faire il devra envoyer les données concernant cet événement au micro-service « webhook ».

Notre service « catalog » devra lui s’abonner à la création d’une promotion. Il devra également envoyer ses informations d’abonnement au micro-service « webhook ».

Notre service « catalog » va donc s’inscrire à un événement en provenance de « promotion » afin d’écouter ce qui se passe lors d’une création de promotion et ainsi effectuer les tâches nécessaires, à savoir les mises à jour des prix des produits.

L’inscription du service « catalog » peut donc être représentée de la sorte :

• le service souhaitant s’abonner : «catalog »
• le service auquel je souhaite être abonné : « promotion »
• le type d’évènement auquel je m’abonne « creation »
• l’url (route) à exécuter lors de cet évènement

« https://catalog.service.com/webhook/update_prices »

Notre service s’est donc abonné au service promotion et souhaite exécuter la route https://catalog.service.com/webhook/update_prices lors de la création d’une promotion.

L’émission de l’événement du service « promotion » peut lui être représentée de cette manière :

• le service qui émet l’événement : « promotion »
• le type d’événement émit : « creation »
• la donnée utile à cet événement : code unique de la catégorie affecté par la
promotion

Notre micro-service « webhook », quant à lui, aura stocké l’abonnement de « catalog »ainsi que l’émission de l’événement de « promotion ».

Nous sommes maintenant capables d’interpréter les informations suivantes : un événement (handler) de création de promotion a été crée. Vérifions qui est abonné à ce type d’événement pour ce service. Nous allons donc voir que notre service « catalog » est abonné à cet événement. Il ne reste plus qu’à exécuter la route d’abonnement avec les paramètres de l’événement. C’est une commande de notre service « webhook » qui sera en charge d’exécuter tous les handlers en fonction des subscribers.

Après avoir mis en place notre système d’exécution des tâches, nous avons rapidement ajouté une option de « tentatives». Imaginons que pour une raison quelconque la tâche n’est pas pue s’exécuter correctement, il faut que cette dernière puisse être relancé lors du prochain dépilement des handlers. Mais dans certains cas cette pratique nous a semblés s’avérer être un problème. Nous avons décidé d’ajouter un paramètre optionnel à nos handlers, à savoir un nombre maximum de tentatives d’exécution avant d’abandonner cette tâche. C’est assez naturellement que nous avons alors introduit la notion d’historisation des handlers.

Historisation des tâches

Dès lors qu’une tâche a atteint son objectif, ou que le nombre de tentatives maximum est atteint, nous historisons cette dernière avec quelques informations supplémentaires dont le code retour de cette exécution. Cela nous permet d’avoir une vision sur chaque tâche et de sa bonne exécution ou non, et d’en analyser les raisons dans ce dernier cas.

Cas pratique d’un environnement Symfony / Api Platform

Nous allons détailler la mise en place technique de notre système de souscription via webhook à travers des micro-services développés avec le framework API PLATFORM. Cet exemple peut bien entendu s’appliquer à tout micro-service Symfony.

Il est à préciser que nous utiliserons les attributs Php8 dans le déroulé de cette présentation et de nos mises en œuvre technique.

Le but de ce cas pratique est de mettre en avant un workflow complet via un environnement technique posé.

Micro-service « webhook »

Notre micro-service doit permettre les créations, Éditions et suppressions des subscribers et handlers, ainsi que gérer l’exécution des différentes tâches.

Dans notre exemple notre endpoint de base sera ;http://webhook.service.com

Micro service « catalog »

Notre service catalog doit s’abonner à la création d’une promotion. Pour se faire nous avons développé un bundle Symfony gérant des attributs custom Php8.

Une fois le bundle installé voici comment s’abonner à un service :

  • Créer un dossier src/Controller/Webhook sur le micro-service catalog
  • Créer ensuite une classe PromotionWebhookController
<?php
namespace App\Controller\Webhook;
use Symfony\Component\Routing\Annotation\Route;
use Restarteco\WebhookBundle\Annotation\WebhookSubscriber;
/**
* Class TestWebhook */
#[Route(path: "/webhook/promotion_create", name: "webhook_promotion_create", methods: ["POST"])] #[WebhookSubscriber(event: "create", service: "promotion")]
class TestWebhook
{
public function __invoke() {
#CODE TO EXECUTE => UPDATE PRODUCTS PRICES FROM GIVEN CATEGORY
}
}

Le bundle dispose d’une commande permettant l’envoie et le stockage de l’abonnement vers le service webhook. Pour sauvegarder notre abonnement nous lancerons la commande suivante :

bin/console restarteco:create:subscriber

Cette commande peut être exécutée au besoin. Dans notre réflexion, nous avons imaginé que son exécution lors du déploiement du micro-service concerné semble être une solution idéale.

Micro-service « promotion »

Lors de la création d’une promotion, notre service doit envoyer le handler au micro-service webhook.

Pour se faire nous passons par un event « POST_WRITE » d’API Platform (ou tout autre event Symfony selon le cas).

<?phpnamespace App\EventSubscriber;use ApiPlatform\Core\EventListener\EventPriorities;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use App\Entity\Promotion;
final class PromotionEventSubscriber implements EventSubscriberInterface {/** @var HttpClientInterface */
private
public function __construct(HttpClientInterface $webhookClient)
{
$this->webhookClient = $webhookClient
}
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ['emitWebhookHandler', EventPriorities::POST_WRITE]
];
}
public function emitWebhookHandler(ViewEvent $event)
{
$promotion = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if (!$promotion instanceof Promotion || Request::METHOD_POST !== $method) { return; }

$data[] = $promotion->getId();

$this->webhookClient->request('POST', 'handlers', ['body'=>json_encode($data)]);
}
}

Dans note exemple nous effectuer l’envoi de l’événement de façon synchrone mais il est tout à fait possible d’effectuer cet envoi de façon asynchrone via un système de message type RabbitQM ou autre.

Exécution des tâches

Notre micro-service « webhook » sera en charge de l’exécution des tâches via une simple commande : bin/console app:execute-handler

C’est à ce niveau que va se mettre en place le système d’historisation des handlers selon le nombre de tentatives paramétré.

Pour terminer nous pouvons résumer tous ces processus via le schéma ci-dessous.

--

--