API Pub/sub

Prérequis

Avant de suivre ce tutoriel vous devez au préalable :

Présentation

Le publish/subscribe (pub/sub) est un système de communication asynchrone entre les éditeurs (publisher) et les consommateurs (subscriber). Les éditeurs envoient des événements (création, modification, suppression de données) au service pub/sub sans se préoccuper de comment ni de quand ces événements seront traités. Le pub/sub distribue ensuite les informations aux consommateurs qui se sont abonnés aux événements.


L'api publish/subscribe de SpinalCom est basée sur la version 4.X de la librairie socket.IO, permettant la communication bidirectionnelle et en temps réel, par conséquent cette api peut être consommée que par socket.IO.


Cet article est un tutoriel pas-à-pas expliquant le fonctionnement et la mise en place d'un système client de l'api pub/sub de SpinalCom avec socket.IO.

Tutoriel : Développer un consommateur (subscriber) d'api pub/sub Spinalcom avec nodejs

A - Initialisation du projet

> cd spinal-api-pubsub-client                                                /* Deplacement dans le dossier du projet */

> npm init -y                                                                /* Initialisation du package.json dans le projet nodejs */

B - Installation Socket.IO


Pour consommer les api publish/subscribe de Spinalcom, il faut dans un premier temps installer la librairie client de socket.IO dans le dossier spinal-api-pubsub-client. Ci-dessous quelques méthodes d'installation.  Si vous utilisez un autre gestionnaire de package, reportez-vous à la documentation d'installation de socket.IO


/* Via CDN */

<script src="https://cdn.socket.io/4.4.1/socket.io.min.js" integrity="sha384-fKnu0iswBIqkjxrhQCTZ7qlLHOFEgNkRmK2vaO/LbTZSXdJfAu6ewRBdwHPhBo/H" crossorigin="anonymous"></script>


/* Via npm */

npm install socket.io-client


/* Via yarn*/

yarn add socket.io-client


NB: SpinalCom utilise la version 4 de socket.IO serveur. Veuillez vérifier la compatibilité des versions avant d'installer socket.IO client.

C - Connexion au serveur

Après l'installation de la librairie client, nous pouvons désormais instancier et nous connecter au serveur d'api. Pour cela créez un fichier à la racine du projet nommé index.js avec le contenu ci-dessous en fonction du lien entre votre client et votre serveur.

// index.js


const io = require("socket.io-client");

const options = {auth: {token: "XXX"}, transports: ["websocket"]} /* Pour plus de details sur les options, consulter la documentation */

const socket = io(options);


// index.js


const io = require("socket.io-client");

const options = {auth: {token: "XXX"}, transports: ["websocket"]} /* Pour plus de details sur les options, consulter la documentation */

const socket = io("http://www.le_lien_de_mon_serveur.com", options);

Le code ci-dessus permet d'instancier le socket client. Après avoir été initialisé, le client essaie d’établir une connexion websocket avec le serveur. Le serveur s'il reçoit la requête de connexion traite les informations reçues, identifie le client et renvoie au client un événement connect (le client a été identifié et connecté avec success) ou connect_error (la connexion n'a pas pu s'établir).

D - Ecouter les événements à la connexion au serveur

Le Client peut écouter les events (connect et connect_error) envoyés par le serveur pour connaitre la raison de l’échec de connexion, ou pour poursuivre avec d'autres taches. Pour cela nous devons utiliser la méthode "on" de socket. Ci-dessous le code à ajouter au fichier index.js.


// index.js


/* Exécutée une fois que la connexion au serveur est établie */

socket.on("connect", () => { console.log("la connexion au serveur a été établie avec succès") });


/* Exécutée en cas de déconnexion au serveur */

socket.on("disconnect", (reason) => { console.log("Déconnexion du serveur pour la raison :",reason) });


/* Exécutée en cas d'erreur de connexion au serveur */

socket.on("connect_error", (err) => {  console.log(err.message) });

E - Souscription aux données (scénario)

1- Comment ça marche ?

L'abonnement aux données se fait en deux (2) étapes. Dans un premier temps le client envoie une requête subscribe au serveur contenant toutes les informations (contexte et nœud) des données auxquelles il veut s'abonner, une option (pour définir s'il faut se souscrire à un sous graph du nœud).

L'api serveur à son tour, répond avec une liste d’événements à écouter par le client et/ou un message erreur pour les données non existantes, ensuite au changement le serveur envoie les données au client.

2 - La requête subscribe

La syntaxe de la requête subscribe se fait comme suit :  socket.emit("subscribe", élément1,...,élémentN, options)

paramètres :

les valeurs auxquelles on veut s'abonner, ils peuvent être regroupé dans une liste ou se suivre successivement en tant que paramètre. les deux (2) formats acceptables sont contextId/nodeId et {contextId: contextId, nodeId: nodeId, option?: option} . contextId et nodeId étant respectivement les ids (dynamic id ou static id) du contexte et du nœud, option n'est pas obligatoire dans l'objet, il permet de définir une option spécifique à l'élément.

les options globales à tous les éléments , ils permettent d'indiquer à quel nœud supplémentaire on veut se souscrire (les enfants du nœud, une arborescence dans le contexte à partir du nœud). options est un objet qui a deux (2) propriétés:

NB : le dernier paramètre est automatiquement considéré comme les options globales, si vous ne souhaitez pas passer une option mettez un objet vide à la fin des paramètres.


3 - Réponse à la requête subscribe (subscribed)

Comme indiqué ci-dessus à la requête subscribe, le serveur répond avec un événement subscribed en envoyant un objet ou une liste d'objets contenant la/les réponse(s) pour chaque élément auxquels le client s'est souscrit.

 L'objet de réponse contient :

La syntaxe en javascript ci-dessous :

socket.once("subscribed",(result) => {

  if(!Array.isArray(result)) result = [result] // convertir le result en tableau s'il ne l'est pas

  // Eccouter les événements ICI

})

4- Ecouter les événements

Une fois la réponse reçue, le client à son tour doit parcourir la réponse et écouter aux événements envoyés  s'il n'y a pas d'erreur, la syntaxe en Javascript ressemble au code ci-dessous :

if(!Array.isArray(result)) result = [result] // convertir le result en tableau s'il ne l'est pas


// parcourir le resultat

result.map(({ error, eventNames }) => {

    if (error) {

        console.error(error); // afficher le message d'erreur

        return;

    }


    // ecouter les evenements

    eventNames.forEach(eventName => {

       socket.on(eventName,(error, dataChanged) => {

  // Faire quelque chose avec dataChanged la nouvelle donnée

       });

    });

});

dataChanged : contiendra les informations du nœud qui a changé, c'est un objet ayant comme propriété :

event : un objet du type  {name: updated , nodeId: "id du nœud modifié" }

info : un objet contenant les infos du nœud

element : un objet contenant l'element du nœud

F - Le module subscribe :

Maintenant que nous savons comment marche la souscription, nous allons implémenter le processus d'abonnement en tant que module, pour éviter la répétition à chaque souscription. Créons un fichier nommé subscribe.js à la racine du projet avec le contenu ci-dessous.


// subscribe.js


module.exports = function subscribe(socket, elements,options,onChangeCallback) {

   socket.emit("subscribe",...elements,options);

   socket.once("subscribed",(result) => {

      if(!Array.isArray(result)) result = [result];

  

      result.map(({ error, eventNames }) => {

           if (error) {

               console.error(error);

               return;

           }


           eventNames.forEach(eventName => {

              socket.on(eventName, onChangeCallback)

           });

       });

   })

}

G - Souscription aux données (code)

Réprésentation via le studio du réseau GTB

Réprésentation via l'api REST du réseau GTB

Dans cette section, nous allons utiliser le module créé précédemment pour nous souscrire à un réseau GTB (voir les images ci-dessus) qui représente les disponibilités des places d'un parking. Complétons l'index.js pour nous abonner à toutes les places du parking sous-sol (arborescence du parking sous-sol dans le contexte Reseau GTB.)

/* index.js */


const subscribe = require("./subscribe.js");

const io = require("socket.io-client");


const options = {

 auth: { clientId: "XXX", secretId: "XXX" }, transports: ["websocket"]}; /* Pour plus de details sur les options, consulter la documentation */

const socket = io(options);


/* Exécutée une fois que la connexion au serveur est établie */

socket.on("connect", () => {

 console.log("la connexion au serveur a été établie avec succès");

});


/* Exécutée en cas de déconnexion au serveur */

socket.on("disconnect", (reason) => {

 console.log("Déconnexion du serveur pour la raison", reason);

});


/* Exécutée en cas d'erreur de connexion au serveur */

socket.on("connect_error", (err) => {

 console.log(err.message);

});



const elementToSubscribe = "38135984/38147024"; // à remplacer par les bons ids

/*

La variable ci-dessus peut aussi être déclaré comme suit :

const elementToSubscribe = "SpinalContext-29bd465f-6c46-3367-67d0-14c79ffc5c45-17e0b35b32a/38147024";

ou

const elementToSubscribe = "38135984/SpinalNode-68090d6a-bc11-2364-34bb-279855b22d81-17e0b35cf88";

ou

const elementToSubscribe = "SpinalContext-29bd465f-6c46-3367-67d0-14c79ffc5c45-17e0b35b32a/SpinalNode-68090d6a-bc11-2364-34bb-279855b22d81-17e0b35cf88";

ou

const elementToSubscribe = {

   contextId: SpinalContext-29bd465f-6c46-3367-67d0-14c79ffc5c45-17e0b35b32a,

   nodeId : 38147024

}

*/


const requestOptions = {

  subscribeChildren: true,

  subscribeChildScope: "tree_in_context"

};


subscribe(socket, elementToSubscribe, requestOptions, (dataChanged) => {

   console.log(dataChanged); // exécuter à chaque dans le réseau GTB

});

Maintenant que le code a été intégré, nous allons tester notre projet. Pour cela ouvrez le terminal et tapez la commande  node index.js à la racine du projet.