touche-and-sell
18 juin 2019

Touch & Sell – Quand le code legacy devient un boulet pour notre stack technique…

Vincent Robert, aujourd’hui lead technique chez Touch & Sell, a rejoint l’entreprise en 2015 avec pour mission de reprendre l’application Rails existante, de la maintenir puis de la faire évoluer.

Dans l’article qui suit, il nous raconte ce qui l’a conduit avec son équipe à migrer vers Rails 5 et comment ils ont repensé la gestion des données. Identification des points bloquants, stratégies et difficultés, Vincent nous partage l’histoire de la stack de Touch & Sell et son évolution ces 3 dernières années ! 

Un peu d’histoire et de contexte

Touch & Sell propose une solution d’aide à la vente qui s’articule autour de la mise en place de deux composants : T&S Studio et T&S Performance.

T&S Studio permet de créer en toute autonomie une application servant de support en RDV commercial qu’on appelle Touch & Sell Performance. T&S Studio est un canal d’information et de communication dédié où l’on retrouve la documentation commerciale à jour, disponible 24/24 pour tous et à partir d’une seule source. C’est un centre de pilotage avec tableaux de bords et données sur l’utilisation des contenus en RDV. L’interface offre une gestion centralisée des droits en fonction des profils et des rôles.

T&S Performance est l’application tactile dans laquelle se retrouvent tous les composants nécessaire à l’élaboration d’un scénario de vente fluide et engageant pour le client. L’utilisateur retrouve alors les scenarii adaptés aux interlocuteurs et aux étapes du parcours de vente.

J’arrive chez Touch & Sell en 2015 pour reprendre le studio développé en Ruby on Rails. L’application existait depuis 3 ans et souffrait d’un historique de multiples développements peu coordonnés.

Touch-Sell en quelques chiffres

  • 1 seul serveur sur le Public Cloud OVH : 16 cœurs 64 Go RAM
  • 200 clients et 10 000 utilisateurs
  • 1 Lead Tech + 2 devs Rails + 6 développeurs mobiles
  • Ruby 2.6.1
  • Gems : Devise, Sidekiq
  • Front : jQuery + Semantic-UI
  • Stats : 82 contrôleurs (concerns inclus), 95 modèles (concerns inclus), environ 20k Code LOC, et 8K Test LOC, 
  • Couverture de code testé : 52%
  • Dépôt dans un Gitlab interne
  • 5,4 commits en moyenne par jour
  • 1 sprint par mois & 1 déploiement en production à la fin du mois
  • Quelques déploiements en hotfix durant le sprint pour des correctifs
  • Déploiements continus sur un environnement de staging pour les recettes à chaque push
  • Moyenne de 400k requêtes HTTP / jour
  • Moyenne de 6M requêtes BDD / jour
  • Pic à 10 requêtes HTTP / seconde

Les limites de la stack 

Absence de bonnes pratiques 

Aucunes bonnes pratiques de Rails ne sont appliquées dans le code : mauvaise utilisation des concerns, pas de mass-assignments, pas de callbacks et globalement une méconnaissance du langage Ruby. J’ai l’impression de lire le code d’un développeur Java.De plus, beaucoup de traitements natifs de Rails sont implémentés manuellement, et en général au mauvais endroit.

Mon code côté serveur ou bien client ?

En parcourant le code, on se rend vite compte qu’il a été difficile de choisir où mettre la logique de l’application : certains morceaux se retrouvent alors du côté serveur, d’autres au niveau du front et de l’UI. C’est au final très coûteux pour l’équipe : nous passons notre temps à recoller des logiques du serveur au niveau du client. Un autre problème concerne les performances. Ces dernières sont impactées à cause de logiques côté JavaScript trop complexes et puis des algorithmes aberrants et non optimisés. Il y a par exemple un tri implémenté en JavaScript dans une liste de 10000 objets. Il devient alors urgent de trouver une solution : l’application rame et les utilisateurs commencent à ne plus avoir confiance.

La gestion des fichiers 

Les fichiers se retrouvent un peu partout sur le serveur selon le code qui les utilise. Nous ne savons pas vraiment quels fichiers sont utilisés ou non. Certains sont peut-être obsolètes. Au final, la gestion de l’espace disque chez notre hébergeur en devient très complexe. Il va donc falloir organiser un peu mieux tout cela !

De premières initiatives pour améliorer la stack

Une migration vers PostgreSQL

La principale fonction de notre application est la gestion de l’arborescence des contenus de nos clients. Et il est impossible de faire des requêtes optimisées avec MySQL sur une structure arborescente. Nous décidons donc de passer à PostgreSQL.

Une migration vers Docker

Notre migration vers Docker implique de connaître parfaitement tous les emplacements disques à faire gérer par Docker. Cela nous permet de mieux organiser nos fichiers.Docker vient aussi nous aider au niveau de nos déploiements, qui échouaient toujours à cause de problèmes de configuration ou de services manquants.

Définition d’un nouveau modèle de données

Nous décidons de revoir notre modèle de données, et migrons vers un modèle plus scalable. Nous gardons en tête qu’il y avait beaucoup d’utilisation du principe Single Table Inheritance là où il ne fallait pas, et pas assez où il fallait ! Par exemple, nous revoyons l’architecture du stockage de l’arborescence de nos clients pour tirer profit de PostgreSQL.

Au final, cette optimisation nous permet de minimiser le nombres de requêtes SQL quand nous devons calculer une arborescence complète, passant de plusieurs secondes à quelques centaines de millisecondes pour les plus grosses.

Refonte graphique de l’interface web

Enfin, nous décidons de nous attaquer à la refonte graphique de l’interface web.

Vers une migration de Rails 5

En démarrant la refonte graphique, nous voulons que la transition soit la plus simple possible pour nos utilisateurs. Nous faisons le choix d’une refonte graphique compatible avec notre ancienne version. Les utilisateurs doivent pouvoir basculer entre l’ancienne et la nouvelle interface graphique. Pour cela, nous gardons la même base de données et la nouvelle interface devra alors s’appuyer sur le modèle de données existant.
Pour la refonte graphique, nous décidons de mettre le maximum de logique côté serveur. Concrètement, le serveur ne va plus se contenter d’envoyer tous les objets à la page qui les mettait en forme en JavaScript. Il doit désormais faire aussi le tri, la recherche, la pagination. Ceci est en fait plutôt simple à réaliser, car ces fonctionnalités existent déjà dans Rails 5, alors que dans la précédente version il fallait faire cette logique du côté de JavaScript. Au final, ce changement nous aide beaucoup pour traiter le cas de données volumineuses.

À la fin de l’écriture de la refonte graphique, nous nous rendons compte que cette refonte est en fait limitée par les capacités de notre serveur au niveau de la gestion des fichiers. Cela devient vite bloquant dans notre stratégie à long terme, à savoir pouvoir aider nos clients à générer leurs propres contenus dans notre application. Nous devons donc trouver une solution !

Revoir notre gestion des fichiers

Nous étudions les systèmes de stockage de fichiers en Rails : Paperclip, Carrierwave et ActiveStorage, le système inclus dans Rails 5. Nous utilisions déjà Paperclip sur d’autres projets et nous n’étions pas convaincu. Carrierwave semblait suivre le même modèle que Paperclip en plus complexe. Nos premiers tests lors d’un POC sur ActiveStorage sont convaincants. Nous partons donc sur cette techno !

Petite anecdote, quelques semaines après notre migration à ActiveStorage, l’équipe de Paperclip annonçait qu’ils dépréciaient le projet. Je suis content qu’on ait misé sur le bon cheval.

La réalisation d’un POC pour valider notre choix

Nous réalisons un projet POC  pour apprendre à utiliser ActiveStorage pour se familiariser un peu plus avec l’outil mais aussi identifier les principales points qui pourraient nous bloquer : 

  • le drag’n’drop de fichier depuis le navigateur, 
  • la gestion d’erreurs en cas de fichiers problématiques

Nous essayons donc de les résoudre dans ce POC. Cela nous permet de créer un module générique apportant les comportements supplémentaires à ActiveStorage dont nous avions besoin. Nous utilisons ensuite ce module dans notre application pour faciliter la migration.

Le choix d’ActiveStorage pour la gestion des fichiers

Concernant la gestion des fichiers et données, tout a été à revoir !

Comme nous stockions des chemins de fichiers, il y a déjà des problèmes de cohérence à résoudre. Ces chemins étant stockés de manière relative, et à partir de racines différentes selon les endroits, il a fallu migrer un par un tous les fichiers déclarés dans la base de données. 
Mais il a aussi fallu migrer ceux qui n’étaient pas déclarés ! Historiquement, certains fichiers étaient stockés à des chemins calculés à partir du chemin d’autres fichiers ! C’est LA RAISON pour laquelle nous avons attendu d’avoir une très bonne vision fonctionnelle du produit avant de faire cette refonte. Nous devions être confiant que nous n’allions pas oublier un cas d’usage.

Mise en place d’ActiveStorage

Ensuite, il a fallu s’adapter au contrainte d’ActiveStorage, le nouveau système de stockage de Rails. Celui-ci permet de s’affranchir du backend de stockage, que ce soit le disque ou un stockage dans le Cloud comme Amazon S3 ou Google Cloud Storage. On perd notamment l’écriture direct dans un fichier, une logique qui semble évidente avec des fichiers locaux mais qui l’est beaucoup moins avec un stockage distant. Avec un stockage distant, il faut télécharger le fichier localement avant de le manipuler puis le renvoyer sur le stockage distant une fois modifié. Ce changement de paradigme nous a obligé à réécrire toutes les logiques de manipulation de fichiers que nous avions développées. Encore une fois, heureusement que nous avions une très bonne vision fonctionnelle de tous les cas d’usage.

Mais ActiveStorage nous a également beaucoup apporté ! Une fois que nous avions ces logiques refaites de manière générique, il a été beaucoup plus simple d’ajouter des post-traitements sur les contenus que nous stockions : génération automatique de miniature, retaillage à la volée des images… Nous avons ajouté tous les quick-wins que l’on envisageait depuis longtemps.

Un autre avantage d’ActiveStorage est que la base de données possède à tout moment la liste de tous les fichiers utilisés ou non. Cela nous permet une meilleure gestion de fichiers abandonnés
Notre bilan d’infrastructure est étonnant, nous avons réduit de 50% notre espace de stockage le jour de la migration vers ActiveStorage.Tous ces gains ont également beaucoup aidé dans la communication sur cette refonte au reste de l’équipe.

Éviter les régressions

Afin de vérifier la procédure de migration de données, nous avons monté 2 nouveaux serveurs avec la même image des données clients. Puis nous avons appliqué notre processus de migration sur l’un d’entre eux. 

À la fin de la migration, nous pouvions donc analyser les résultats en masse, en comparant le serveur migré et le serveur original. À chaque erreur, nous revoyions le processus de migration et remontions le snapshot de données, puis nous recommençions. Nous avons faire cette manoeuvre  plusieurs dizaines de fois mais au final, nous avons réussi à avoir un processus capable de migrer les données existantes sans impact direct sur nos clients.

L’heure du bilan

  • La migration Rails ainsi que tous les stockages de fichiers sont terminés en one-shot.
  • Il est devenu beaucoup plus facile de tracker les fichiers non utilisés. 
  • Avec ce nouveau système de gestion de données, qui permet de générer à la volée tous les fichiers d’un client à la demande, de façon répétée ou de manière massive, nous maîtrisons notre espace de stockage. Même lorsque celui-ci explose et est très sollicité.
  • Le plus coûteux lors de cette migration a été la recette fonctionnelle

Nos erreurs et ce que l’on a appris

  • Mettre plus de logique dans le modèle Rails
  • Si vous pensez que vous avez mis assez de logique dans le modèle Rails, ce n’est pas encore assez !
  • Allez encore un peu… vous pouvez sûrement remonter encore un peu de logique dans le modèle !
  • Plus sérieusement, nous utilisons désormais beaucoup plus la console Rails pour consulter les données, réparer, modifier. 
  • Nous avons également mis en place une API REST à disposition des clients
  • En déplaçant au maximum notre logique applicative dans nos modèles, il est aujourd’hui plus simple d’avoir des comportements cohérents dans l’ensemble du système (console, UI, API). Les contrôleurs et les vues contenant moins de logique, on peut également se concentrer plus facilement sur l’interface graphique. On peut alors prendre le temps de la repenser, de modifier ses comportements, sans être contraints par des soucis de cohérence des données.
  • Nous avions été contraints par les logiques métier trop présentes dans les contrôleurs lors de notre première refonte graphique. En s’efforçant de conserver les modèles existants, il était contraignant de ré-implémenter des logiques applications dans les contrôleurs. Suite à cette migration, nous avons appris à déplacer notre logique applicative dans les modèles, ce qui nous rend plus serein pour d’éventuelles refontes graphiques à venir.

Merci à Vincent d’avoir partager un morceau de l’histoire de la stack de Touch & Sell.

2 postes de développeur·se fullstack Ruby On Rails sont d’ailleurs à pourvoir. Consulter les offres