L’outillage du repository est structurant pour la performance de nos processus de développements. Néanmoins, certaines typologies sont parfois oubliées.
En tant qu’ingénieurs nous aimons les outils. Nous pouvons avoir tendance à perdre de vue leurs objectifs et leur contribution à l’organisation. Le repository est central dans nos flux de développements supportant l’expérimentation et l’apport de valeur.
Notre mission est donc de construire un système performant au service du Business par notre approche de Quality Engineering. Nous devons balancer la productivité d’aujourd’hui et de demain par la complexité et l’évolutivité des outils intégrés.
Cet article couvre les typologies d’outillage nécessaires pour sécuriser la gestion de votre repository en y considérant la gestion de dette technique. Les outils mentionnés n’ont pas l’objectif d’être exhaustifs ou priorisés. Nous ne parcourons pas les sujets de documentation, IDE et de communication.
Les catégories d’outils sont ordonnées dans les étapes de notre processus de développement. Démarrons donc par la gestion du code dans notre repository.
Tout commence par un gestionnaire de sources
Le premier sujet est bien de stocker notre code sur un répertoire partagé, accessible, durable, sécurisé, extensible. Cela nous semble aujourd’hui trivial par l’évolution des plateformes disponibles et la réduction des coûts de stockage.
La gestionnaire de source doit pouvoir fournir des workspaces, hiérarchisés en particulier pour un multirepo ou polyrepo. Ces modèles auront tendance à générer une multitude de repository qui doivent être organisés, comme une bibliothèque et ses livres.
Pour tout modèle, les fonctionnalités de recherche de code, projet et composants sont nécessaires. Réaliser des recherches sur un large repository est d’ailleurs parfois supporté par des moteurs de recherches spécifiques au code. Cody, Lerna ou Octolinker sont des solutions possibles.
La gestion des accès sera au support d’une organisation structurée du repository. Le but n’est pas de bloquer les accès par défaut, cela dépendra de la culture d’engineering que nous voulons créer. L’objectif principal est de clarifier les responsabilités des différents projets pour le reste de la chaîne de développement.
Nous devons avoir une attention particulière au cas du monorepo. Sa croissance et centralisation créent le besoin d’une recherche plus rapide, de télécharger localement uniquement les projets impactés, et de gérer des packages. Atlassian partage de bonnes pratiques dans ces articles Monorepos in Git and How to handle big repositories in Git. Microsoft disponibilise également Rush pour la gestion des packages.
Dans un modèle de split-repo, une solution est nécessaire pour passer d’un monorepo à un multirepo avant le build. Split.sh est une référence. Une myriade d’outils est également disponible pour gérer des migrations et séparation de repository : Bazel, Tomono, ou cette liste de shopsys.
Les équipes peuvent ensuite commencer à contribuer sur la base de code.
Un gestionnaire de version supportant les revues
Le versioning est supporté par le concept des commits et les modèles de gestion de branches. La coordination, orchestration et processus de commits est le véritable sujet à outiller.
Les pratiques de développement ont évolué pour systématiquement intégrer des revues entre pairs. Cette pratique nécessite de supporter des mécanismes de pre-merge, pre-submit ou de hooks plus flexibles. Les solutions de CI incorporant Git supportent nativement ces mécanismes. Il est possible de complémenter son approche avec Pre-commit.
Les revues de code sont efficaces quand elles combinent processus automatisés et manuels.
L’automatisation doit se focaliser sur les tâches répétitives et sans valeur ajoutée pour un humain. Il est important de sécuriser une réelle gouvernance et inclusion dans les processus des équipes pour capter leur valeur. Des standards permettront également la réplicabilité, scalabilité et maintenabilité des outils mis en place.
SonarQube et Codacy sont des outils qui remplissent cette tâche. Des alternatives supportent plus précisément les revues, tel que Pull Review.
Les processus manuels ont trois types d’exécutions : manuels, hybrides et automatiques. Certains processus ont besoin d’une initiative, réalisation et contrôle complètement humain. D’autres, comme les peer reviews, sont hybrides pour avoir une partie du processus automatisé. Certains processus, même si complètement automatisés, nécessitent une revue régulière manuelle des résultats obtenus, à l’instar d’une IA.
Nous précisons ensuite d’outillage pour construire nos applications, sans oublier d’en gérer leurs dépendances.
Un système de build et de gestion des dépendances
Le build de notre application est une pièce angulaire dans la vie de notre logiciel. Son objectif est de construire les exécutables qui seront ensuite installés et exécutés dans les différents environnements. C’est donc à cet endroit que la complexité inhérente au code se matérialise.
Les monorepos grandissant nécessitent de contenir le temps de build pour rester utilisables. Il faut donc pouvoir compiler et tester uniquement les dépendances pertinentes. Bazel est issu de Google, Buck de Facebook, Pants de Twitter. Des solutions inspirées de Yarn émergent également, comme Baur ou Oao.
Le modèle de multirepo peut nécessiter une gestion de dépendances en fonction de leur implémentation. L’utilisation de librairies partagées créera des dépendances à gérer entre vos différents projets. Une mauvaise implémentation peut d’ailleurs créer des dépendances cycliques à détecter via des outils d’analyses statiques.
Des bots apparaissent pour aider dans la gestion des dépendances, tels que dependabot. Leur utilisation est principalement axée sur la mise à jour de versions. Les analyses plus complexes nécessitent un outillage plus poussé, comme c’est le cas de Google.
Une fois que nos dépendances sont correctement gérées nous pouvons aborder le build des applications. On retrouve ici les solutions traditionnelles de Continuous Integration (CI), parfois spécifiques à certaines technologies. Mentionnons ici Jenkins, Gitlab CI, Azure DevOps, Bitbucket.
Des mécanismes de refactoring at scale
L’ensemble des changements réalisés sur la base de code créeront une complexité qu’il faut contenir. Les mécanismes de revues seuls ne sont pas suffisants étant limité au prisme d’un seul changement. Il faut donc un outillage qui supporte une approche plus large de refactoring at scale.
Des humains peuvent réaliser des analyses globales sur notre base de code. Néanmoins, la taille, complexité et vélocité des changements réalisés requiert un outillage en support. Imaginez le temps nécessaire pour manuellement parcourir l’ensemble d’un monorepo ou 100 repos pour identifier des axes d’améliorations.
Les acteurs de référence ont significativement investi sur cette problématique, conscient du risque de dette technique et de ralentissement associé. Google a par exemple créé Rosie allant jusqu’à réaliser des propositions de refactoring automatiques. D’autres solutions sont disponibles comme Turbolift de Skyscanner, Autorefactor, ou Dependabot.
Le refactoring fonctionnel reste la tâche la plus adaptée aux humains aujourd’hui. Le contexte métier et possibles évolutions étant difficilement modélisable pour l’instant. En parlant de modélisation, celle de nos objets principaux combine analyse statique, gouvernance et automatisation.
Une gouvernance des APIs et de la donnée
La data a évolué comme un actif de plus en plus valorisé pour les organisations. Pour certaines, c’est leur principale raison d’être. La cohérence des données est donc structurante pour leur collecte et exploitation.
L’accélération de création de données fait émerger le besoin d’outillages. Les applications sont à l’origine de la création des différentes données accessibles dans l’organisation. Les développeurs spécifient à un moment la structure des données qui seront générées. La démultiplication et distribution des équipes nécessitent une réelle gouvernance.
Des outils ont émergé en fonction des usages. Les dictionnaires de données ont historiquement émergé pour les bases de données relationnelles. Ils permettent de valider la cohérence au build et runtime des modèles. Les modèles événementiels commencent à être supportés par une gestion de schémas. L’écosystème convergence autour de cette gouvernance la data et espérons le, avec des standards comme asyncAPI.
L’étape suivante est d’avancer avec le déploiement de l’application dans les différents environnements, sans oublier de tester.
Un outillage de test intégrés à notre chaîne
Le test est une pièce structurante dans notre chaîne de livraison pour tout modèle de repository. Le point important à considérer est son intégration avec notre modèle d’organisation de code.
L’étape de construction de notre application doit inclure les tests à réaliser au plus proche du code. Les solutions de CI supportent l’exécution de tests unitaires, d’intégrations ou autres typologies qui peuvent être intégrées au build de l’application. Un monorepo nécessite une approche spécifique du test pour permettre de premièrement tester uniquement les modules impactés.
Nous arrivons ensuite aux étapes de déploiements dans les différents environnements. Nous devons avoir la capacité d’exécuter des tests comme étapes de notre pipeline CI/CD, également connus sous le nom de quality gates. D’autres tests doivent au contraire être en dehors de la pipeline en fonction de l’organisation, comme ceux fonctionnels ou performance de bout-en-bout.
En bout de chaîne, le monitoring est une typologie de testing que nous devons inclure dans une démarche plus large d’Observability. C’est d’ailleurs ce qui nous permettra de réagir en cas d’incident sur une fonctionnalité délivrée. Notre réactivité sera améliorée par l’utilisation d’activation et de désactivation, connus sous le nom de features flags.
Des mécanismes d’activations et de retour arrière
La capacité à gérer des activations de fonctionnalités requiert d’inclure ce besoin dès l’étape de conception. Nous devons évaluer sa pertinence, son périmètre et ses règles d’activation au regard de la valeur et réduction du risque apportées.
Gérer des features flags nécessite du code implémentant sa règle et mécanisme d’activation durant l’exécution. Nous pouvons réaliser cela avec du code basique composé de “if” et une variable dans un fichier de configuration. Les outils disponibles se basent sur ces deux concepts clés.
Un outillage plus avancé permet d’assurer d’autres besoins de continuité de service, accessibilité via une interface, gestion des droits. Une solution externe à l’application facilite également une vision transverse et moins couplée. Les solutions A/B Tasty, LaunchDarkly ou Split.io sont des acteurs reconnus.
Les features flags nécessitent néanmoins une gestion de leur cycle de vie, jusqu’à leur suppression. Leur durée de vie sera différente selon les usages, certains peuvent être continus. Nous devons combiner pratiques et outillages pour y arriver. Vous pouvez consulter cet article sur la suppression des features flags, Piranha d’Uber ou les recommandations de Featureflags.io.
La visibilité est donc un élément fondamental pour une capacité de pilotage de nos repos.
Des reporting, tableau de bord, et le Process Mining
Gardons en tête que nos choix d’organisation, processus et outillages sont au support d’une meilleure création de valeur de l’engineering. Nous devons pouvoir mesurer et visualiser notre système pour guider notre pilotage.
Le suivi de notre repository en volume de code, activités de commits et autres métriques sont supportés par des graphiques relativement standards. Certaines solutions mentionnées ci-dessus fourniront nativement une visualisation. Nous pouvons également utiliser des solutions de dashboard et de reporting telles que Grafana, Kibana ou PowerBI.
Nos activités sont d’ailleurs rarement statiques. Nos chaînes de développement sont des successions d’événements, étapes et séquences composants des processus. Les concepts d’architectures orientées événements, de value-stream et de Flow ont émergé. C’est ici que le Process Mining apparaît pour mesurer et visualiser ces processus.
Des outils au service de votre engineering productivity
La gestion d’un repository requiert une approche bien plus large que le seul stockage du code. Nous comprenons qu’une réelle architecture est nécessaire au travers des différentes typologies d’outils à intégrer.
Pour garder le cap, nous devons garder un réel focus sur les objectifs de notre entreprise. Une approche technologique créera une complexité par la multiplication des solutions qui finira par être contre-productive.
Notre système doit permettre de supporter des cycles d’itérations rapides, supportant la croissance et la maintenabilité de notre base de code. La pertinence des fonctionnalités proposées, activées ou désactivées reste propre à chaque organisation.
La mise en place d’un tel système d’engineering peut être discutable d’un point de vue business. C’est pourquoi une telle démarche doit être alignée, partagée et reliée à des enjeux métier.
Le code de notre organisation n’est-il pas l’un de ses principaux actifs ?
Références
A curated list of repos tooling https://github.com/korfuri/awesome-monorepo
https://medium.com/@SkyscannerEng/turbolift-a-tool-for-refactoring-at-scale-70603314f7cc
https://blog.bitsrc.io/11-tools-to-build-a-monorepo-in-2021-7ce904821cc2