Blog tech

Gems, paquets, containers, mais pour JavaScript

Rédigé par Numa Claudel | 23 octobre 2015

Aujourd’hui je vais vous présenter un outil permettant d’empaqueter des ensembles de méthodes JavaScript, qui pourront ensuite être partagées et réutilisées: node-machine.

node-machine permet donc la distribution de paquets via npm, mais en apportant une structure plus stricte à l’élaboration de ceux-ci, un guide pour la définition de l’API, des sorties et de la documentation. node-machine n’a donc pas vocation à remplacer npm. Si je devais faire une comparaison, alors npm serait à bundler ce que MachinePacks serait aux gems.

L’intérêt premier de ce type d’outil est donc la réutilisation du code, mais ici en plus poussé puisqu’il permet et encourage le partage de paquets. On peut de plus y trouver d’autres avantages comme :

  • offrir une API documentée, des descriptions, des exemples
  • des méthodes avec une API plus stricte (paramètres requis, typage)
  • un meilleur contrôle des erreurs (chaque méthode gère ses erreurs)
  • des sorties prédéfinies

Ces paquets son principalement destinés pour une utilisation au sein d’une application Node.js, mais il est aussi possible, en moins aisé, de les utiliser dans le navigateur. Il va de soit que cela dépend également de la fonction du paquet. Voici ce que dit la FAQ à ce sujet.

Utilisons un paquet existant

Commençons par regarder la liste des MachinePacks disponibles sur le site. Cette liste est disponible directement sur la page d’accueil ou bien sur une page dédiée aux MachinePacks. Elle est conséquente mais je suis sûr qu’au premier survol vous apercevez déjà des paquets qui vous intéressent.

Je vous propose de commencer par regarder comment se présente l’un de ces paquets, par exemple en tout début de liste: Age. Cliquez dessus pour arriver sur la page de présentation du paquet Age.

On y trouve une brève description et des informations générales comme la version, comment l’installer et l’utiliser dans un projet, les contributeurs, ainsi que les méthodes proposées. Cliquez sur la méthode calculate pour voir apparaître sa documentation.

On y trouve :

  • la description de la méthode
  • une structure vide de l’implémentation
  • une description du paramètre dateOfBirth avec le type attendu et son caractère obligatoire
  • les sorties de la méthode et leurs descriptions : error, invalidDateFormat , success

On peut aussi survoler des portions de code avec la souris pour avoir le détail dans un tooltip. On a là une belle documentation !

Maintenant passons à l’essai, prenons Session.

Je vous propose de démarrer un nouveau projet Sails. Si vous ne connaissez pas Sails, je vous invite à visiter le site du projet, ainsi qu’à consulter mon précédent article sur le sujet.

sails new tryNodeMachine
cd tryNodeMachine

Intégrons le MachinePack Session au projet :

npm install machinepack-session --save

Et passons à l’utilisation. Créons un contrôleur Session:

sails generate controller session

Que l’on complète avec ceci :

// api/controllers/SessionController.js

var Session = require('machinepack-session');

module.exports = {

  index: function(req, res) {
    res.view();
  },

  // Save a string value to the current session under the specified key.
  save: function(req, res) {

    Session.save({
      key: req.param('session_key'),
      value: req.param('session_value')
    }).setEnvironment({
      req: req
    }).exec({

      error: function(err) {
        res.negotiate(err);
      },

      success: function() {
        res.view('session/index');
      }
    });
  },

  // Load a string from the session using the specified key.
  load: function(req, res) {

    Session.load({
      key: req.param('session_key'),
    }).setEnvironment({
      req: req
    }).exec({

      error: function(err) {
        res.negotiate(err);
      },

      notFound: function() {
        res.view('session/index', {
          error: 'Clé non trouvée'
        });
      },

      success: function(result) {
        res.view('session/index', {
          session_entry: result
        });
      }
    });
  },

  // Delete the specified key in the session.
  del: function(req, res) {

    Session.del({
      key: req.param('session_key'),
    }).setEnvironment({
      req: req
    }).exec({

      error: function(err) {
        res.negotiate(err);
      },

      success: function() {
        res.view('session/index');
      }
    });
  }

};

Concrètement ici, pour l’exemple je me contente de définir la méthode index, histoire de pouvoir naviguer sur cette même vue, et ensuite d’interfacer les méthodes disponibles dans le MachinePack Session avec des méthodes du contrôleur. En redirigeant toujours sur cette page d’index sur laquelle je vais afficher les valeurs, nous pourrons ainsi voir ce qui se passe. Hormis l’ajout de setEnvironment, je me suis très simplement contenté de copier/coller le code proposé dans la documentation de chaque méthode en complétant juste les réponses. setEnvironment sert à passer l’instance de connection dont a besoin le paquet.

Et donc la vue session/index:

<!-- view/session/index.ejs -->

<h1>Manipuler la session avec le MachinePack Session</h1>

<% if(typeof error !== 'undefined') { %>
  <p>
    Erreur : <%= error %>
  </p>
<% } %>

<h2>Ajouter une entrée à la session</h2>
<form action="/session/save" method="POST">
  Clé : <input type="text" name="session_key" />
  Valeur : <input type="text" name="session_value" />
  <input type="submit" value="Ajouter" />
</form>

<h2>Récupérer une entrée de la session</h2>
<p>
  <form action="/session/load" method="GET">
    Clé : <input type="text" name="session_key" />
    <input type="submit" value="Afficher" />
  </form>

  <% if(typeof session_entry !== 'undefined') { %>
    Entrée demandée : <%= session_entry %>
  <% } %>
</p>

<h2>Supprimer une entrée de la session</h2>
<form action="/session/del" method="DELETE">
  Clé : <input type="text" name="session_key" />
  <input type="submit" value="Supprimer" />
</form>

<h2>Contenu de la session</h2>
<p>
  Session : <%= JSON.stringify(session) %>
</p>

Un formulaire par méthode, avec un rendu du contenu actuel de la session (qui est directement disponible dans la vue), l’affichage du paramètre récupéré pour la méthode load et l’affichage des erreurs éventuelles.

Un sails lift à la racine du projet, et vous pouvez maintenant faire des essais à cette url http://localhost:1337/session. Envoyez une paire de clé/valeur, récupérez ou supprimez en une. Vous observerez dans le paragraphe qui rend le contenu de la session, que le MachinePack Session fait bien son travail.

Création d’un paquet

En reprenant les grandes lignes du guide, voici les étapes proposées pour créer et partager un paquet.

Premièrement nous avons besoin de Yeoman, nécessaire pour le générateur generator-machinepack.

Si vous ne l’avez pas installé :

npm install -g yo

Puis installez le générateur :

npm install -g generator-machinepack

Et enfin l’interface en ligne de commande pour la création de MachinePack :

npm install -g machinepack

Ensuite il suffit de se laisser guider par l’assistant à la création, qui va entre autres vous demander un nom et une description:

yo machinepack

Une fois ce paquet créé, diverses commandes permettent de l’interroger, le tester ou encore le modifier. En me référant à cette section, voici certaines commandes à utiliser à l’intérieur du dossier du paquet :

  • mp ls
  • mp exec <machine>
  • mp add
  • mp info

La commande mp est un raccouci pour machinepack. Ces commandes permettent respectivement de lister les machines dans le paquet, d’en exécuter une, d’en ajouter une ou d’avoir quelques informations sur le paquet. D’autres commandes sont disponibles, vous pouvez en consulter la liste grâce à la commande mp --help ou en utilisant l’autocomplétion.

Après avoir ajouté une ou plusieurs machines au paquet avec la commande mp add, il faut les compléter. 3 sections pour cela :

  • inputs qui sont les paramètres à passer à la machine
  • exits qui sont les sorties possibles de la machine : erreurs ou succès
  • fn qui est le corps de la machine, son processus

Pour les inputs et les exits il faut préciser leur nature obligatoire ou non, rédiger une description et donner un exemple. Les types attendus sont déduits des exemples. Ces informations seront ensuite reprises pour générer la documentation. La fonction prend donc en paramètres inputs et exits et définit la logique de chaque sortie.

Par ailleurs une machine peut très bien dépendre d’une autre machine et utiliser ses services, qu’elle soit externe ou interne au paquet. Il suffit de les inclure en dépendance avec un require et :

  • de les installer avec npm install <machinepack> --save si elles sont externes au paquet
  • de les inclure avec require('machine').build(require('./<the-machine-name>')) si elles sont internes au paquet

Lorsque vous jugez que votre paquet est suffisament abouti, vous pouvez le publier via npm. Voici la section qui en parle, ainsi que les spécifications pour la création de machine et un exemple de paquet.

Conclusion

Qu’est ce qu’on en retire ? node-machine nous offre une solution standardisée et robuste pour partager un ensemble de méthodes servant le même dessein. Ce “standard” apporte contrôle et stabilité grâce à un typage des paramètres attendus, des sorties et erreurs contrôlées par le paquet et une belle documentation.

L’équipe Synbioz.

Libres d’être ensemble.