La migration continue

Actualité ekino -

Un retour d'expérience d'une refonte technique

Article paru le :
Article paru le :

On a tous été confronté un jour à devoir refondre un site web (sujet déjà abordé dans l’article Expériences de refonte) avec bien souvent les mêmes problématiques à gérer : migrer les données, porter les fonctionnalités existantes et en développer de nouvelles.

Ce que je souhaite partager ici est un retour d’expérience sur une migration technique d’un site e-commerce initié en 2008. Sept années plus tard, l’heure des mises à jour a sonné ! Avec une telle durée de vie, on imagine facilement la richesse des fonctionnalités mais aussi qu’une dette technique s’est confortablement installée. Les enjeux sont multiples mais le principal est d’éviter une longue phase de développement avec à la clé la peur de la bascule finale. Par ailleurs, le site doit continuer à vivre durant la migration, on doit donc être en mesure d’apporter des bug fixes et également de faire évoluer, dans une certaine mesure, quelques parties.

Challenge accepted, let’s go!

Avant tout, un audit technique de l’existant a été réalisé pour confirmer le constat de plateforme vieillissante et le besoin de refonte. L’objectif de cet audit était aussi d’imaginer des solutions de migration ; l’une d’elles a été privilégiée et validée au travers d’un POC (Proof Of Concept). On a ainsi pu détecter et valider très tôt quelques points difficiles (partage de session, portée des variables…). Cela a aussi permis d’automatiser certaines tâches, comme la génération de règles de redirection des URL legacy vers les nouvelles.

Ce chantier de refonte comporte également un autre aspect : la montée en compétences de deux développeurs de notre client sur la suite d’outils et processus de développement appliqués au projet. L’objectif est de les former aux bonnes pratiques et de les accompagner pour qu’ils deviennent autonomes. De nouveaux sujets sont ainsi mis en place : framework et conventions de codage, travail collaboratif en pull requests, intégration continue et déploiement automatisé… Même si elle a aussi bénéficié d’une refonte complète, je ne présenterai pas l’infrastructure, le véritable défi étant côté applicatif.

Après la théorie, la pratique

Historiquement, il s’agit d’un osCommerce au code procédural. Le cœur a été modifié pour répondre aux besoins métier, rendant une montée en version très compliquée. Pour cette refonte, PHP est conservé et Symfony2 a été retenu avec une stratégie de migration continue. Le but est de parvenir à faire cohabiter osCommerce et Symfony2 et migrer progressivement les fonctionnalités. On évite ainsi le fameux effet tunnel de la longue phase de développement évoquée précédemment. De plus, plutôt que de devoir maintenir la version legacy et développer la nouvelle, les efforts sont tournés uniquement sur la nouvelle.

Symfony2 est dorénavant en charge de traiter toutes les requêtes HTTP. Pour cela, il encapsule le legacy. Cependant, toujours dans cette optique de migration continue, il nous faut une solution plus fine pour migrer non pas page par page, mais fragment de page par fragment de page (aussi appelé bloc par la suite). Le legacy doit donc pouvoir interroger Symfony2 pour rendre un bloc. Il faut un système performant qui ne crée pas d’overhead et qui soit capable de séparer la logique métier de la vue. Cette tâche revient à SonataBlockBundle qui répond parfaitement à ces besoins au même titre qu’un « include » PHP mais bien plus élaboré et qui ne détériore pas les performances (comme le ferait une sous-requête1 par exemple). Le seul pré-requis est de rendre le container de Symfony2 global pour être accessible partout depuis le legacy et ainsi permettre l’appel au service de rendering de SonataBlockBundle. Un avantage supplémentaire de SonataBlockBundle est qu’il intègre SonataCacheBundle. On est ainsi en mesure d’appliquer des politiques de cache par bloc. Le portage des blocs se fait donc au travers de services Symfony2 qui intègrent une nouvelle implémentation.

migrate

Ceci étant fait, on arrive rapidement sur un problème prévisible : le partage de sessions entre legacy et Symfony2. Autrement dit, être en mesure d’accéder aux variables de session du legacy depuis Symfony2. La difficulté réside dans le fait que Symfony2 stocke les données de session dans des « bags » sous une clé particulière « _sf2_attributes » (configurable) de la super globale $_SESSION. Cela signifie que Symfony2 ne sait pas accéder à des données stockées dans des clés arbitraires comme par exemple $_SESSION[‘customer_id’]. Pour résoudre ce problème, un LegacyBag a été développé et est instancié et enregistré dans Symfony2 à la demande (s’il n’existe pas déjà) via un LegacySession.

En prévision de la fin de la migration, le code qui n’est plus censé agir est placé dans un LegacyBundle. Des listeners et autres compiler pass y sont utilisés pour limiter au maximum les besoins propres à la phase de migration. Ainsi une fois la migration terminée, il suffit de débrancher ce bundle pour que le site soit full Symfony2.

Conclusion

Au moment de la rédaction de cet article, la migration est toujours en cours. Cette approche, que j’ai voulue partager ici, montre qu’une refonte technique peut être menée à bien autrement que d’un seul tenant. Cette façon de faire apporte en plus un effet rassurant et motivant pour tout le monde : les mises en production fréquentes (au moins une par semaine) valident le travail des développeurs et le client ne peut qu’apprécier de voir son projet avancer au fur et à mesure. Un autre avantage est de pouvoir mesurer rapidement les bénéfices de la refonte en terme de performance et d’expérience utilisateur. En revanche, cela peut générer une certaine frustration : s’agissant ici d’une migration purement technique, les changements pour l’utilisateur final du point de vue front ne sont pas aussi perceptibles.

Comme toutes les refontes où il y a une reprise de code, le travail de reverse engineering est souvent complexe. La démarche présentée ici ne le simplifie pas puisqu’il faut isoler plus finement le code à migrer. On peut d’ores et déjà dire que ce choix pour ce projet est le bon mais il faut garder à l’esprit qu’une fois la migration commencée, il faut la terminer sous peine d’être dans un état intermédiaire contribuant ainsi à la dette technique.

Notes :
1. [Sous-requête : 5 ways to optimize symfony baseline performance] retour