Aller à la navigation

Blog

Retour à l’accueil

31 Jan 2020

[DEV] Utiliser un seul repository pour plusieurs projets avec l’approche Monorepo

Une stratégie de gestion des versions nommée Monorepo

Monorepo signifie littéralement Monolithic Repository. L’idée principale est de regrouper l’intégralité des projets fortement ou faiblement liés ensemble dans un seul répertoire de gestion de versions. Ainsi, un Monorepo est composé d’un répertoire SCM (Source Control Management, il s’agit d’outils de contrôle de versions du code tels que les très célèbres Git, SVN ou Mercurial) et d’un nombre fini de projets.

L’utilisation du Monorepo se révèle intéressante dans un contexte où plusieurs répertoires (donc plusieurs projets) sont interdépendants. Lorsque beaucoup de fonctionnalités ou de produits différents sont développés au sein d’une même équipe, il arrive fréquemment que des morceaux de code de répertoires différents évoluent en même temps. En effet, la corrélation est généralement forte entre plusieurs modules pourtant censés être indépendants. Cela a pour effet de fortement augmenter le coût de la maintenabilité, car il faut en effet maintenir des versions différentes d’un répertoire de code source en fonction de son utilisation dans les autres projets. Une évolution trop structurelle d’un des projets entraînerait des répercussions sur le code des autres projets qui en dépendent. Ainsi, la maintenabilité et l’effort pour réaliser une telle évolution pourraient nuire à la bonne réussite du projet dans sa globalité.

Le contrôle de l’ensemble des projets avec Monorepo est donc réalisé de manière centralisée, commune et surtout beaucoup plus simplifiée. En effet, dans ce type de stratégie, il n’est plus nécessaire de gérer différentes versions d’un projet pour l’inclure dans un autre.

Dans un type de contexte aux multiples projets, il peut être judicieux d’accentuer la relation qui existe entre les différents répertoires hébergeant les projets, afin de les gérer comme une seule entité autonome.

Si un projet A passe de la version 1.5 à 1.6 alors qu’il est utilisé par le projet B, il faut manuellement modifier la version pour utiliser la dernière. Cependant, il n’est pas toujours nécessaire d’aller modifier cette version, par exemple lorsque le projet n’utilise pas la nouvelle fonctionnalité développée dans le projet A en version 1.6. Cependant, dans le cas d’une montée de version majeure (où la version passe de la 1.6 à la 2.0 par exemple), des changements peuvent être plus structurants et peuvent alors impacter le code du projet B. Toutefois, dans le cas d’une gestion indépendante des projets, il ne faudra pas mettre à jour le projet B dans le cadre d’une livraison et il faudra maintenir plusieurs versions (la 1.6 et la 2.0) du projet A pour que tout continue de fonctionner. Si ce scénario se répète dans d’autres projets avec d’autres dépendances, la gestion des projets deviendra rapidement ingérable à cause des dépendances croisées et des versions trop nombreuses. On paiera alors le prix d’un lourd développement supplémentaire lors de la mise à jour simultanée de plusieurs projets, rendant ainsi la maintenance complexe.

Dans le cadre d’une stratégie de gestion de versions Monorepo, chaque projet est livré en même temps que les autres car la gestion des livraisons est unifiée. Cela a pour effet de réduire considérablement l’impact du problème précédemment présenté, car l’ensemble du répertoire ne contient qu’une seule et même version. Cela garantit ainsi l’intégrité des dépendances car tous les projets sont livrés en même temps, de façon unique et simplifiée.

 

Les avantages

La stratégie de CI/CD (Continuous Integration/Continous Delivery) se trouve grandement simplifiée et centralisée si l’on utilise une stratégie Monorepo car la livraison ne se configure qu’une seule fois et devient unique. La gestion des dépendances et les montées des versions des projets deviennent plus simples car la maintenance se fait au fur et à mesure des livraisons. Le cycle de livraison est donc plus rapide (et ainsi plus proche du “Time To Market”) et les différents artefacts peuvent être livrés en parallèle sans qu’un ordre ne soit imposé par un quelconque workflow.

La gestion des dépendances simplifiée implique une meilleure optimisation de la compilation et permet également une meilleure réutilisation du code existant car chaque bloc de code peut être extrait dans un artefact différent. Ce dernier est alors importé dans les différents sous-projets qui ont besoin de ses fonctionnalités. La notion de synchronisation des versions prend alors toute son importance lors des livraisons uniques.

Non seulement le Monorepo permet de simplifier le refactoring de code, puisque chaque projet évolue en même temps que les autres, mais permet également d’instaurer des bonnes pratiques communes. En effet, si plusieurs équipes différentes collaborent sur un Monorepo, il leur sera plus simple de s’inspirer des normes déjà instaurées dans le code et de les suivre afin de faire en sorte que les structures et les pratiques de code soient parfaitement homogènes et cohérentes entre elles. Les développeurs peuvent alors partager des pratiques de développement communes, les mettre en confrontation et choisir la plus adaptée pour chaque besoin, en faisant bénéficier à l’équipe l’intégralité de leurs connaissances.

Un premier exemple avec SBT

Bien évidemment, de nombreux systèmes de gestion de dépendances permettent d’appliquer une stratégie de type Monorepo (tel que Maven) mais ici nous allons nous concentrer sur SBT, privilégié pour les développements de type Scala. Considérons l’arborescence de projets ci-dessous pour l’exercice :

 

Voici une manière de configuration le fichier build.sbt afin d’obtenir une stratégie Monorepo :

lazy val myMonorepoProject =
             (project in file(« . »))
             .aggregate(projectA, projectB, projectC)

lazy val projectA = (project in file(« projectA »))
lazy val projectB = (project in file(« projectB »))
lazy val projectC = (project in file(« projectC »))

On remarque donc qu’il y a très peu de complexité dans ce type de configuration. En effet, il suffit de déclarer une variable (ici nommée “myMonorepoProject”) qui enveloppe l’intégralité des projets contenus dans le répertoire. Ici, la fonction “aggregate” prend en paramètres les trois projets (sous forme de variable) nommé projetA, projetB et projetC. Ces derniers sont déclarés via leur chemin dans l’arborescence des fichiers via l’instruction “in file”, tandis que la variable myMonorepoProject est déclaré via la racine de l’arborescence. Il suffit ensuite de rajouter les “settings” nécessaires comme on le ferait dans n’importe quel projet géré via SBT. Cependant, il peut être judicieux de stocker les “settings” communs à tous les projets dans une variable afin de la réutiliser pour chaque déclaration, et ainsi améliorer la lisibilité du code en évitant les répétitions.        

Avec ce type de configuration, il devient possible de compiler tout ou partie du Monorepo. Par exemple, si l’on souhaite compiler seulement l’un des projets (ici le projectA), il faut exécuter les commandes suivantes :

sbt:my-monorepo-project> project projectA
sbt:projectA> compile

Pour compiler l’intégralité des projets en une seule exécution, il suffit d’exécuter les commandes suivantes :

sbt:projectA> project my-monorepo-project
sbt:my-monorepo-project> compile

 

Conclusion

 Nous avons pu voir ensemble tous les avantages offerts par une stratégie de type Monorepo, notamment sa gestion centralisée de l’intégralité des projets interdépendants et la souplesse offerte par le déploiement simplifié. L’exemple vu avec SBT s’applique bien évidemment à tous les systèmes de gestion de dépendances bien connus par les développeurs. Cependant, ce type de stratégie peut effectivement ne pas s’adapter à tous les cas d’usages rencontrés et tous les contextes. En effet, si aucun projet n’est dépendant l’un de l’autre au sein d’une organisation ou que les cycles de vies sont uniques et très différents pour chacun des projets, il n’y a aucune raison de penser à migrer vers une stratégie Monorepo. Il est tout de même important de noter que ce type de gestion est utilisé par de nombreuses entreprises très célèbres tels que Google et Facebook.

 

Article inspiré du Scala IO 2019 – Par Thomas ZUK, Ingénieur Big Data FINAXYS

Share our news :

-