Industrialisation - Mise en oeuvre (Partie 2)

12/12/2018

L'abstraction... Et... C'est tout?

Dans un article précédent (Industrialisation - Abstraction), je faisais un petit retour sur la base de l'industrialisation, à savoir le niveau d'abstraction à adopter durant la phase de conception de nos développements. Évidemment on peut s'arrêter là, et pratiquer nous-même de savants copier/coller pour exporter nos devs sur différents projets et ne pas aller plus loin dans la globalisation du dev, ne pas tenir compte de l'amélioration continue etc... Mais voilà, en s'arrêtant ici on va perdre en temps et en qualité ce qu'on avait initié par notre réflexion.

Personnellement, il y a plusieurs choses qui m'ennuient encore si on ne va pas plus loin:

  1. Je n'ai pas envie de comprendre la structure de fichiers d'une fonctionnalité. Je ne veux pas savoir quel fichier doit être placé où pour que cela fonctionne (je grossis volontairement le trait).
  2. Je n'ai pas envie de fouiller dans la liste de tous les anciens projets utilisant cette fonctionnalité pour y apporter une mise à jour. N'oublions pas que toute modification est source de risques de bug.
  3. Je n'ai pas envie de passer mon temps à tester et retester les évolutions dans chaque contexte.
  4. Je n'ai pas envie de passer des heures à analyser le code d'un développeur tiers qui est intervenu sur cette fonctionnalité. En plus, comme chaque développeur est le meilleur du monde, il croit toujours qu'un dev tiers à déféquer au milieu de son code. Or ce n'est pas le cas, la plupart du temps c'est juste que les logiques humaines sont différentes entre deux individus, pour autant le développement n'est pas forcément moins bon.
  5. Je n'ai pas envie d'être le seul à faire évoluer et maintenir cette fonctionnalité. Quelle tristesse d'être le développeur attitré d'une fonctionnalité. Que deviendrait-elle sans moi? Et puis surtout, quelle plaie de passer son temps à revenir sans arrêt sur les mêmes développements! Du neuf que diable!

Heureusement pour nous, il existe des outils et des bonnes pratiques qui permettent de gérer ces points sans effort (parce qu'on n'aime pas trop l'effort inutile). Je m'en vais les décrire ci-après, cependant je vais rester assez vague car je ne veux pas contextualiser mes explications. En effet, ce qui compte ici c'est la méthode, et celle-ci est identique que vous soyez dev web (front ou back), iOS, Java ou .net, etc etc... Peu importe le langage, bossons nos méthodes.

 

Les packages

Dans la mesure où nos développements doivent être décontextualisés logiquement, il est nécessaire de structurer nos sources de façons à ce qu'elles soient indépendantes.
En effet, si on reprend notre exemple des commandes "ananas" du précédent article, j'imagine que vous n'allez pas tout gérer dans un même fichier, vous avez eu la clairvoyance d'atomiser vos éléments logiques au maximum. Vous avez séparé ce qu'on peut considérer comme des événements, de ce qu'on peut considérer comme des objets, de ce qu'on peut considérer comme des modèles, etc... (Attention, je parle ici de logique, je ne fais pas référence à des termes liés à une technologie en particulier). Et, par la force des choses, vous avez probablement regrouper les différents événements ensemble, les différents objets entre eux, et de même pour les différents services. Bien joué.

Maintenant la question est : "Est ce que vous avez regroupé tous les évènements de votre projet au même endroit? Ceux qui concerne la commande ananas et ceux qui concerne d'autres fonctionnalités de votre projet?". Et si vous l’avez fait, vous vous êtes fourvoyé.

En effet, si tous les éléments logiques de plusieurs fonctionnalités sont regroupés ensemble, comment savoir lequel est utilisé pour telle ou telle fonctionnalité. C'est pourquoi la meilleure méthode est de regrouper tous les éléments d'une même fonctionnalité, puis dans ce regroupement, effectuer des sous-ensembles des éléments logiques. De cette manière, on retrouve toujours nos événements avec nos événements, nos objets avec nos objets, mais uniquement ceux d’une même fonctionnalité. Et le premier niveau de regroupement est fonctionnel et non logique. On a donc créé un paquet de notre fonctionnalité. Ainsi il est facile d’isoler les éléments de notre fonctionnalité. On parle de package, de bundle, de module ou autre (ça dépend de la technologie dans laquelle on se trouve), mais la théorie est la même : Isoler notre fonctionnalité pour la rendre indépendante.

En organisant ces éléments techniques de cette manière, on correspond à une logique plutôt facile à appréhender. La logique fonctionnelle est prépondérante par rapport à la logique technique. Dans une maison, vous ne voulez pas savoir où sont ranger les couteaux, vous voulez d’abord savoir où est la cuisine où sont ranger les couteaux…. D’abord le fonctionnel, ensuite le technique.

Les dépendances

En organisant les développements de cette manière modulaire vous allez vite être confronté aux éléments transverses, desquels vont dépendre plusieurs fonctionnalités.

Par exemple, prenons un projet qui propose une fonctionnalité d'inscription d'utilisateur, ainsi qu'une fonctionnalité tout à fait indépendante qui gère un envoi de lettre d'information. Ces deux fonctionnalités distinctes vont toutes les deux nécessiter une fonctionnalité d'envoi de mail, l'un pour confirmer une inscription, l'autre pour ... envoyer un mail....
Donc on a là la fonctionnalité d'envoi de mail qui est commune aux deux autres fonctionnalités. Dans un soucis de mutualisation, on va évidemment créer un module indépendant qui ne va gérer que la fonctionnalité d'envoi de mail. Les deux autres modules d'inscription et de newsletter vont dépendre de ce module. 

Très bien, mais qu'adviendra-t-il lorsque vous utiliserez la fonctionnalité d'inscription sur un autre projet ? Penserez-vous aussi à utiliser le module indépendant d'envoi de mail? Comment saurez-vous que le module d'inscription nécessite ce dernier ? Une Doc? Un readme? Non, bien évidemment, on va utiliser un gestionnaire de paquet pour faciliter l'intégration et la gestion des dépendances de nos paquets.

Les gestionnaires de paquets

Les gestionnaires de paquets vous permettent d'intégrer vos paquets fonctionnels dans vos projets en gérant les dépendances. Si on reprend notre exemple précédent, il nous suffira juste de charger le paquet "inscriptions" pour que notre gestionnaire de paquets charge en plus le paquet d'envoi d'emails nécessaire de manière transparente. Évidemment il faudra mentionner que le module "inscription" dépend du module "envoi d'emails", et ceci de la manière prévue par le gestionnaire de paquets.

Il existe de nombreux gestionnaires de paquets qui sont en général associés à un langage : composer pour PHP ; yarn ou npm pour JS. Pour ces gestionnaires, on définit dans chaque module ses caractéristiques et ses dépendances dans un fichier json.
De la même  manière, on charge nos modules dans nos projets via un fichier json qui liste les modules utilisés dans les versions voulues, et on peut y mentionner où aller chercher nos modules. De cette façon, on peut stocker nos paquets fonctionnels de manière publique ou privée afin de se confectionner une bibliothèque de modules réutilisables. On va pouvoir stocker nos paquets dans un environnement de versioning (gitlab, github, bitbucket ou autre) puisque les gestionnaires de paquets gèrent les versions de modules, ce qui permet de pouvoir charger des versions précises de modules par projet.

 

Voici un exemple de déclaration de module via composer. C'est le fichier qui définit le module content_synchronizer disponible sur drupal.org. Notez bien la gestion de dépendances et de requirements dans la partie "require"  :
 

{
  "name": "drupal/content_synchonizer",
  "type": "drupal-module",
  "description": "Content Synchronizer",
  "keywords": ["Drupal"],
  "license": "GPL-2.0+",
  "homepage": "https://www.drupal.org/project/content_synchonizer",
  "minimum-stability": "dev",
  "support": {
    "issues": "https://www.drupal.org/project/issues/content_synchonizer",
    "source": "http://cgit.drupalcode.org/test"
  },
  "require": {
    "cocur/slugify": ">=2",
    "php": ">=5.6.0"
  },
  "extra": {
    "drush": {
      "services": {
        "drush.services.yml": "^9"
      }
    }
  }
}

 

La centralisation 

Grâce à cette méthode on peut se créer une bibliothèque de packages réutilisables dans différents projets. On mutualise ainsi les évolutions liées à un module et on permet le partage de ces évolutions (ou fixes) sur tous les projets simplement. Étoffée au fil du temps, cette bibliothèque devient un outil formidable pour notre besoin de produire sereinement et en réduisant fortement les temps de développement que la production d'une fonctionnalité dédiée spécifiquement au projet aurait nécessitée.

En revanche la creation d'un module indépendant nécessite une logique particulière puisque le niveau d'abstraction doit être optimal. Pour cela je vous conseille de lire les articles concernant la confection de modules réutilisables : Maintenance - Développez réutilisable et Modularité - Développez réutilisable.

 

Ajouter un commentaire

HTML restreint

  • Balises HTML autorisées : <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Les lignes et les paragraphes vont à la ligne automatiquement.
  • Les adresses de pages web et les adresses courriel se transforment en liens automatiquement.
Votre email ne sera pas publié mais permettra à l'administrateur de vous recontacter en cas de problème