Traiter les dépendances Maven lors de la migration vers Git

Matt Shelton
Matt Shelton
Retour à la liste

Nous migrons vers Git et nous adorons Gitflow. Et maintenant ? Nous allons tester tout ça ! Mon équipe est géniale. Elle a dressé une liste de workflows de développeurs dans Confluence sur la base des travaux qu'elle a effectués et de toutes les tâches bizarres que nous pensons devoir faire à l'avenir. Ensuite, nous avons testé tous les workflows dans une structure de projet similaire à la nôtre (mais sans code, seulement un pom.xml).

Les dépendances Maven allaient bientôt se révéler notre plus grand problème.

Maven et la numérotation des builds Maven crée des builds 1.0.0-SNAPSHOT jusqu'à la livraison. Une fois la livraison effectuée, -SNAPSHOT est supprimé, et votre version se nomme 1.0.0. Votre processus de build doit pouvoir prendre en charge l'incrémentation ultérieure de votre version mineure pour permettre ensuite la création de builds similaires à 1.1.0-SNAPSHOT. Évidemment, vous n'êtes pas obligé d'utiliser trois chiffres comme nous. Vous faites votre choix au début du projet. Quoi qu'il en soit, il est vraiment important de comprendre l'étape de l'« INSTANTANÉ ». C'est la toute dernière étape avant la livraison.

Artefacts

Vous voyez, notre principale préoccupation dans tous ces workflows était de garantir la gestion adéquate des dépendances entre les différentes versions de nos projets et entre nos différents projets.

Chaque fois que les dépendances Maven sont récupérées pour un build, Maven fait, par défaut, un pull depuis Ye Olde Internet(e)TM. Ces artefacts sont stockés localement afin d'accélérer les builds ultérieurs. Pour faciliter ce process, une solution consiste à utiliser un dépôt d'artefact sur votre réseau local qui fera office de cache local pour les dépendances externes. La récupération LAN sera toujours plus rapide que le téléchargement depuis les CDN les plus performants qui soient. Nous utilisons Artifactory Pro pour notre dépôt d'artefacts. Par ailleurs, depuis que nous disposons d'une structure à plusieurs modules, nous stockons également nos artefacts de build dans Artifactory. Quand nous générons l'un de nos packages communs, nous pouvons faire un pull de cette version spécifique via la résolution des dépendances Maven et récupérer l'artefact dans le dépôt d'artefacts.

Ça fonctionne à merveille. Artifactory vous permet également de synchroniser vos artefacts entre des instances. Si vous le vouliez, vous pourriez par exemple l'utiliser pour répliquer votre dépôt de livraison pour les déploiements de production sans avoir à générer un process distinct.

Dépendances Maven, branches de fonctionnalité et pull requests

Tous nos builds vont dans Artifactory. Avec SVN, nous utilisions un dépôt d'instantanés pour conserver les deux builds d'instantanés les plus récents, un dépôt de staging pour tous les builds de livraison en attente d'approbation et un dépôt de livraisons réservé aux builds destinés à la production.[1] Ces builds sont numérotés (comme je l'ai expliqué plus tôt) et peuvent être récupérés par un modèle d'URL prévisible basé sur le dépôt et la version.

Le principal workflow de chaque développeur consistait à créer une branche de fonctionnalité à partir de la branche de développement, à la compléter et à faire une pull request pour merger l'ensemble dans la branche develop. Pour un projet unique, cette méthode fonctionne généralement sans problème. Permettez-moi tout de même de vous décrire le premier problème auquel nous avons été confrontés et qui nous amenés à remettre sérieusement en question la migration complète :

Comme je l'ai déjà dit, nous avons plusieurs couches de dépendance entre nos projets. Au niveau de nos produits, nous avons pour cela une excellente raison, tant historique que stratégique. Nous avons envisagé d'autres architectures susceptibles de résoudre ce problème, mais elles en créaient de nouveaux. Nous pouvons nous simplifier la vie (ce que nous avons fait, j'en parlerai dans un prochain article), mais pour l'instant, conserver notre structure en l'état est un choix stratégique.

Le développeur A, appelons-la Angela, commence à travailler sur une fonctionnalité dans Jira. Cette fonctionnalité nécessite deux branches : une de notre projet commun et une du produit X. La version du projet commun est 2.1.0-SNAPSHOT. La version du produit X est 2.4.0-SNAPSHOT. Elle travaille un peu localement, puis pushe la sauvegarde vers Bitbucket Server. Bamboo récupère ces changements, génère le package commun et charge common-2.1.0-SNAPSHOT dans Artifactory, puis génère productX avec une dépendance dans common-2.1.0-SNAPSHOT, avant de charger également productX-2.4.0-SNAPSHOT. Tests unitaires réussis !

Le développeur B, appelons-le Bruce, commence à travailler sur une autre fonctionnalité dans Jira, pour un produit différent : le produit Y. Cette fonctionnalité nécessite également deux branches : une de notre projet commun et une du produit Y. La version du projet commun est, comme ci-dessus, 2.1.0-SNAPSHOT. La version du produit Y est 2.7.0-SNAPSHOT. Il travaille un peu localement, puis pushe ses changements vers Bitbucket Server. Bamboo récupère ces changements, génère le package commun et charge common-2.1.0-SNAPSHOT dans Artifactory, puis génère productX avec une dépendance dans common-2.1.0-SNAPSHOT, avant de charger également productX-2.4.0-SNAPSHOT. Tests unitaires réussis !

Pendant ce temps, Angela découvre un petit bug dans le code de son produit X et écrit un test unitaire pour valider son correctif. Elle l'exécute localement, et le test est une réussite. Elle pushe ses changements vers Bitbucket Server. Bamboo les récupère et génère le produit X. Le build est réussi, mais certains tests unitaires échouent. Ce ne sont pas les nouveaux qu'elle a écrits, mais les premiers qui étaient liés aux changements initiaux apportés à la fonctionnalité. Bamboo a détecté une régression que son build local n'avait pas repérée. Comment est-ce possible ?

En raison de sa dépendance commune, la copie extraite par Bamboo lors de la génération du produit X n'était plus sa copie. Bruce a remplacé common-2.1.0-SNAPSHOT dans Artifactory à la fin du build de sa fonctionnalité. Aucun conflit de code ne s'est produit, puisque les deux développeurs travaillaient à leurs propres branches chacun de leur côté, mais la source de récupération des artefacts Maven était corrompue.

À se taper la tête contre les murs !

Pendant un mois après la découverte du problème, nous avons tout tenté pour le résoudre. Par le biais de notre RTC[2], nous avons échangé avec les membres de l'équipe Bamboo qui utilisent git-flow et avec le développeur qui gère git-flow, une implémentation Java de git-flow. Ils nous ont tous beaucoup aidés, mais sans un processus nécessitant que chaque développeur travaillant sur une fonctionnalité exécute différentes étapes manuelles, nous n'avons pas trouvé de solution acceptable.

Si vous vous demandez ce que nous avons fait, voici comment nous avons procédé :

  1. Modifier le numéro de version lors de la création de la branche ou juste après.
    • Pour ce faire, vous pouvez utiliser mvn jgitflow:feature-start afin de créer la branche.
    • Nous pouvons utiliser un hook Bitbucket Server ou un githook local.
    • La configuration peut s'effectuer manuellement avec mvn version:set-version une fois la branche créée.
    • Nous pouvons automatiser le changement avec le plug-in [maven-external-version].
  2. Modifier le numéro de version une fois la branche terminée, puis faire un merge du tout dans develop.
    • Pour ce faire, vous pouvez utiliser mvn jgitflow:feature-finish afin de finaliser la branche.
    • Utiliser un pilote git merge pour gérer les conflits de POM.
    • Utiliser un hook post-receive asynchrone dans Bitbucket Server.
  3. Le faire manuellement. (C'est une blague. Nous n'avons pas envisagé cette possibilité très longtemps.)

Chacune de ces options a des effets néfastes. Les développeurs devaient notamment exécuter manuellement des étapes à chaque fois qu'ils avaient besoin d'une branche de fonctionnalité. Et nous leur demandons de créer des branches de fonctionnalité tout le temps. De plus, dans la plupart des cas, nous ne pouvions pas utiliser efficacement les pull requests, ce qui suffisait à nous dissuader.

Nous avons dédié une à deux personnes pendant près de deux mois jusqu'à ce que nous ayons une révélation expliquant pourquoi nous abordions le problème de la mauvaise façon.

Une version unique

Avec le recul, il m'apparaît clairement que notre plus grosse erreur a été de nous concentrer sur les outils git-flow au lieu d'utiliser ceux dont nous disposions déjà pour implémenter les workflows que nous souhaitions. Nous avions :

  • Jira Software
  • Bamboo Server
  • Maven
  • Artifactory Pro

Il s'avère que c'était tout ce dont nous avions besoin.

L'un de nos ingénieurs a eu une brillante idée : comme le problème n'était pas la gestion des builds à proprement parler mais plutôt la réécriture des artefacts, il nous fallait corriger Artifactory. Son idée consistait à utiliser une propriété Maven pour configurer l'URL du dépôt des instantanés sur une URL personnalisée qui comprenait l'ID du ticket Jira, puis à écrire ses artefacts dans un dépôt créé dynamiquement dans Artifactory avec un modèle personnalisé. Le résolveur de dépendance de Maven trouverait les artefacts dans le dépôt d'instantanés de développement s'il n'était pas nécessaire de les séparer en branches, par exemple si nous travaillions simplement sur un produit et qu'il n'était pas commun.

Nous avons configuré cette petite variable de propriété utile dans notre fichier de configuration de build, puis nous avons développé un plug-in Maven pour la renseigner lors de la première étape du cycle de vie du build Maven. Sur le papier, cela semblait incroyable et encourageait l'équipe à travailler dur pour résoudre le problème. Le problème étant que nous ne pouvions pas vraiment le faire. La première étape du cycle de vie Maven est la validation. Avant même l'exécution des plug-ins de validation, les URL des dépôts étaient déjà résolues. De fait, notre variable n'était jamais remplie, et l'URL n'était pas nommée en fonction de la branche. Même si nous avions utilisé une structure dans un dépôt distinct de nos instantanés de développement, elle n'aurait pas été isolée pour le développement parallèle.

À nouveau, c'est à se taper la tête contre les murs !

Après une bière, l'ingénieur dont nous avons parlé plus haut a fait quelques recherches pour trouver un autre moyen d'ajouter des fonctionnalités à Maven : les extensions.

« À l'alcool, la cause et la solution à tous nos problèmes dans la vie ! » - Homer Simpson

Les extensions, comme les plug-ins, vous permettent d'améliorer grandement votre workflow Maven, mais elles s'exécutent avant les objectifs du cycle de vie et elles disposent d'un meilleur accès à Maven. En utilisant le package RepositoryUtils, nous avons forcé Maven à réévaluer ses URL avec un analyseur personnalisé, puis à les réinitialiser avec nos valeurs mises à jour.[3]

Une fois l'extension mise en place et testée, nous avons traité les tâches pré-migration les unes après les autres, en passant de « on n'y arrivera jamais » à « on aura terminé lundi prochain… donc il va falloir que je rédige dix pages de documentation d'ici demain ». Je mettrai bientôt en ligne une nouvelle publication expliquant comment les outils interagissent pour obtenir notre nouveau workflow de développement, ainsi que les enseignements que nous avons tirés sur le processus.

[1] : L'inconvénient était que je devais utiliser un script écrit pour indiquer à l'API REST d'Artifactory de « promouvoir » des builds du staging à la livraison. C'est suffisamment rapide, mais nécessite davantage d'automatisation.

[2] : Technical Account Manager. Plus d'informations ici.

[3] : Après les efforts de développement initiaux, nous avons découvert qu'il nous fallait en faire encore plus pour que cela fonctionne tout le temps : comme quand un instantané Artifactory (d'un autre ingénieur) est plus récent que votre instantané local, Maven choisit l'artefact distant parce que… il est PLUS RÉCENT, donc il est MEILLEUR, non ?

Prêt à découvrir Git ?

Essayez ce tutoriel interactif.

Démarrez maintenant