Réalisations

Lead technique chez Greenflex · 2012-2017

BeeGreen, migrer sans tout arrêter

BeeGreen est un SaaS multi-clients pour suivre un parc d’équipements et gérer des achats responsables. Pendant cinq ans, j’ai fait passer son code PHP de 2009 vers Symfony, page par page, pendant que les évolutions demandées par les clients continuaient de sortir.

  • 5 ansde migration progressive, sans geler les livraisons
  • 1ercontributeur du projet, dans une équipe de 3 à 7 développeurs
  • 181tables décrites avec Doctrine, sur une base restée en service
  • ≈ 230merge requests relues et intégrées
BeeGreen · portail
Le portail : les applications activées pour le client, le suivi de son parc et une demande d’achat en cours de validation. Illustration simplifiée, données fictives.

Le contexte

Un SaaS pour les grandes entreprises

Greenflex accompagne les entreprises dans leur transition environnementale. Son offre logicielle, BeeGreen, réunit plusieurs applications utilisées par de grands comptes : le suivi de milliers d’équipements, de leur achat à leur fin de vie, et un guide d’achats responsables avec ses circuits de validation.

J’ai rejoint le projet en 2012 et j’en suis devenu le lead technique. En plus du développement, j’étais l’interlocuteur direct des clients, presque chaque jour, pour leurs demandes et leurs évolutions.

Une mission de 2012 à 2017 : les outils ont bien changé depuis, la méthode reste celle que j’applique aujourd’hui.

Le point de départ

Un code de 2009 qui avait grandi sans fondations

À mon arrivée, l’application comptait près de 70 000 lignes de PHP, sans framework commun ni test. Les pages faisaient tout à la fois, les identifiants de la base étaient écrits en dur et le schéma évoluait à coups de scripts SQL appliqués à la main.

Tout réécrire aurait gelé les évolutions pendant des mois, avec le risque de casser une application utilisée chaque jour par les clients. J’ai choisi de la transformer progressivement.

Extrait d’une page de 2012

if (!$_acl->isAllowed('item', 1)) {
    include('site-403.phtml');                  // ① droits vérifiés dans la page
} else {
include('include.php');

if (isset($_GET['i'])) {                        // ② paramètres lus directement
    foreach ($_GET['i'] as $pk_item) {
        $item[$pk_item] = $db->getItem(array(   // ③ méthode « magique »
            'fk_customer_item'  => $_SESSION['company']['pk'],
            'pk_item'           => $pk_item,
            'fk_status_item !=' => 283,         // ④ identifiant en dur
        ));
    }
}
// … puis 750 lignes de SQL, de logique et de HTML
La fiche d’un équipement, reformatée. getItem n’existait nulle part : une méthode de 450 lignes devinait la table à interroger d’après le nom de la fonction appelée.

La méthode

Faire cohabiter l’ancien et le nouveau

Dès 2012, j’ai démarré un projet Symfony à côté de l’existant, en décrivant avec Doctrine la base de données en place. Puis j’ai fait de Symfony la porte d’entrée unique de l’application.

Les pages migrées sont servies par Symfony. Les autres passent par un contrôleur de compatibilité qui exécute l’ancien code et l’affiche dans le cadre commun. Connexion, session et droits sont partagés : un seul identifiant pour toutes les applications, au lieu des appels techniques d’une application à l’autre.

Les pages migraient au fil des besoins : la liste des équipements, leur fiche, l’accueil… Et quand une réécriture était trop risquée, l’ancien code restait en place, nettoyé, jusqu’au bon moment.

Le détail technique, sur le blog

Le navigateur demande une page, par exemple /items

Le routeur Symfony cherche une route qui correspond

Page déjà migrée

  1. Route Symfony dédiée
  2. Contrôleur, formulaires et Doctrine
  3. Template Twig

Page pas encore migrée

  1. Route « attrape-tout », déclarée en dernier
  2. Contrôleur de compatibilité : exécute l’ancienne page et capture son affichage
  3. Réponse Symfony, avec le menu commun

Partagé par les deux

  • Connexion unique
  • Session et droits
  • Données de chaque client isolées
  • Base PostgreSQL
Chaque page réécrite en Symfony prend automatiquement le pas sur l’ancienne, sans changer son adresse. Le jour où une page est migrée, il suffit de supprimer l’ancien fichier.

Ce qui a changé

Le socle transformé, livraison après livraison

Personne ne me demandait de reprendre le code ni l’interface : ce travail de fond n’avait pas de budget à part. Je l’ai mené par conscience professionnelle, en profitant de chaque fonctionnalité livrée pour assainir le code qu’elle touchait et faire évoluer l’interface.

  • Une base de données décrite et versionnée

    En 2012

    Des tables génériques, des évolutions de schéma appliquées à la main et des identifiants de la base écrits en dur dans le code.

    En 2017

    Les tables décrites avec Doctrine, des migrations versionnées, des clés étrangères, et des constantes à la place des identifiants.

    Des évolutions de schéma versionnées, rejouées à l’identique sur chaque environnement.

  • Des pages qui ne font plus tout

    En 2012

    Des pages de plus de 1 000 lignes qui mêlaient contrôle d’accès, lecture directe des paramètres, SQL, logique métier et HTML.

    En 2017

    Contrôleurs, formulaires et templates Twig séparés. Dans l’ancien code encore en place, les requêtes extraites dans des classes et les vues découpées.

    La liste des équipements passée de 1 293 à 477 lignes, avant même sa migration.

  • Une interface commune

    En 2012

    Des tableaux de mise en page, des styles écrits dans le HTML et une interface différente d’une application à l’autre.

    En 2017

    Un framework CSS, des feuilles de style organisées et bien nommées, un menu et une mise en page partagés, et une interface que j’ai fait évoluer au fil des livraisons.

    Les anciennes pages affichées dans le même cadre que les nouvelles : l’utilisateur ne voit pas la différence.

  • Un projet outillé

    En 2012

    Aucun framework commun, aucun test, une configuration et des mises en production faites à la main.

    En 2017

    Symfony, des conventions de code (PSR, PHP CS Fixer), des tests Behat, une configuration par environnement, un environnement de développement reproductible et un déploiement automatisé.

    Une mise en production en une commande, avec un environnement de recette par client.

Cinq ans de migration

Une transformation par étapes

  1. 2012

    Premiers pas

    Grand nettoyage des pages les plus utilisées, index en base, migrations de schéma. En juillet, le projet Symfony démarre, sur une version du framework encore en bêta.

  2. 2013

    Faire cohabiter

    Le contrôleur de compatibilité permet de servir l’ancien code depuis Symfony. Le suivi du parc et les alertes passent sur le nouveau socle.

  3. 2014

    Un seul produit

    Connexion unique pour toutes les applications, nouvelle interface graphique, API du catalogue sur Elasticsearch, déploiement automatisé.

  4. 2015

    Une équipe outillée

    Revues de code par merge request, montées de version du framework, suppression des modules devenus inutiles.

  5. 2016-2017

    Le relais

    L’équipe poursuit la migration (nouvelles versions de Symfony, découpage front, back-office et API). De mon côté : intégration de Qlik Sense et documentation.

Ce que j’ai construit

Des applications complètes, de la conception à la mise en production

La migration ne s’est jamais faite au détriment du produit. En parallèle, j’ai conçu et développé plusieurs applications entières de la suite, de la base de données à l’interface, et de nombreuses fonctions pour les autres.

  • Des achats responsables

    Un catalogue de matériel noté sur des critères environnementaux, des kits, un panier et des demandes d’achat avec un circuit de validation propre à chaque client.

  • Le suivi d’un parc d’équipements

    Une fiche par équipement, des mouvements unitaires ou en masse, un historique, des champs personnalisés par client, des imports et des exports.

  • Un portail commun

    Un tableau de bord à widgets et des applications activées client par client, comme dans un magasin d’applications.

  • Un SaaS multi-clients

    Les données de chaque client isolées automatiquement à chaque requête, des droits par fonction et une interface en six langues.

  • Des alertes sur mesure

    Des alertes et des rapports envoyés par e-mail, configurés à partir de requêtes sur les données du client.

  • Données et tableaux de bord

    Une API du catalogue avec recherche à facettes sur Elasticsearch, puis l’intégration de tableaux de bord Qlik Sense avec connexion unique.

PHP · Symfony · Doctrine ORM et Migrations · PostgreSQL · Twig · Elasticsearch · Behat · Qlik Sense

Au-delà du code

Lead technique, entre les clients et l’équipe

  • Au contact des clients

    En lien presque quotidien avec les clients : demandes, réclamations, cadrage des évolutions, et des améliorations que je leur proposais.

  • Embarquer l’équipe

    Conventions, revues de code et migrations outillées, pour une équipe passée de 3 à 7 développeurs, dont certains habitués à d’autres façons de travailler.

  • Accepter le provisoire

    Laisser dans l’ancien code ce qu’il était trop risqué de réécrire, plutôt qu’une migration parfaite qui casse ce qui marche.

  • Transmettre

    Documentation d’installation et de traduction, présentation de l’état de la migration aux nouveaux arrivants, qui l’ont poursuivie.

Et pour vos projets

Ce que j’en retiens aujourd’hui

  • Pas besoin de tout réécrire

    Une migration page par page apporte de la valeur pendant toute la transition, là où une refonte complète fait attendre des mois.

  • Le socle fait partie de chaque fonctionnalité

    Sans budget dédié au travail de fond, j’ai assaini le code touché par chaque livraison. C’est ce qui a permis d’avancer pendant cinq ans.

  • Les outils changent, la méthode reste

    Symfony 2 a bien vieilli depuis, mais j’applique la même approche aujourd’hui à des applications React et Symfony récentes.

À la même époque, chez Greenflex, j’ai aussi reconstruit de zéro un guide d’achats responsables : lire l’étude de cas EcoGuide IT.