- L’adoption d’un monorepo présente des avantages comme la cohérence de l’organisation, le partage de code et le renforcement d’un environnement d’outillage commun, mais reproduire tel quel les exemples des géants de la tech conduit à de nouveaux problèmes et défis
- Pour qu’un monorepo soit un succès, il faut respecter le principe selon lequel toutes les tâches majeures doivent être en O(change) et non en O(repo), ce qui exige des outils et des stratégies adaptés à chaque étape du build, des tests et du CI/CD
- Pour le contrôle de source, on commence généralement avec Git, puis il faut envisager une montée en charge progressive avec le sparse checkout, les systèmes de fichiers virtuels, etc.
- Pour le système de build, il est recommandé de conserver autant que possible un seul langage, de tenir au maximum avec les outils de build natifs de chaque langage, puis de migrer progressivement vers Bazel, Buck2, etc. uniquement si cela devient indispensable
- Pour les tests et le CI/CD, il faut détecter rapidement uniquement la portée des changements afin de build, tester et déployer, et dans les grands monorepos, des stratégies de fiabilité comme la relance automatique des tests et l’isolation des flaky tests sont également indispensables
Introduction : le début du parcours vers le monorepo
- Pour un ingénieur d’une nouvelle équipe Developer Productivity, la décision d’adopter un monorepo amène rapidement à se demander quels préparatifs et quels efforts seront nécessaires
- Les bonnes pratiques de grandes entreprises comme Google, Meta ou Uber paraissent impressionnantes, mais il est en réalité impossible d’atteindre exactement le même niveau de résultats
- Chaque organisation doit décider d’adopter un monorepo en fonction de ses propres raisons et besoins, et peut y rechercher des bénéfices en matière de cohérence (consistency), d’intégration organisationnelle et d’outillage partagé
Clarifier la nécessité du monorepo
- Les exemples des grandes entreprises ne sont que l’état auquel elles sont finalement parvenues, et ne constituent pas une base pertinente pour démarrer
- En pratique, de nouveaux problèmes apparaissent, avec des types d’incidents différents de ceux des systèmes de gestion de multiples dépôts
- L’objectif d’un monorepo est de maintenir la cohérence, d’unifier l’outillage à l’échelle de l’organisation et d’appliquer des standards et conventions d’ingénierie
- Chaque équipe doit définir clairement des objectifs adaptés à sa propre culture et à sa propre direction afin d’obtenir des résultats efficaces
La règle d’or : le principe de O(change)
- Tous les outils liés au dépôt doivent avoir une complexité en O(change) et non en O(repo) pour rester rapides
- Plus un monorepo à grande échelle grossit, plus l’inefficacité des outils existants devient visible ; une conception structurelle capable de surmonter ces problèmes de performance est donc indispensable
- Les innovations évoquées dans les blogs techniques des grandes entreprises visent elles aussi, pour la plupart, à corriger les inefficacités liées au O(repo)
Contrôle de source
- La plupart des organisations logicielles utilisent Git par défaut, mais Git a des limites de performance lorsqu’il doit s’étendre à grande échelle dans un environnement de monorepo centralisé
- En pratique, la plupart des organisations peuvent tenir très longtemps avec git+GitHub
- À mesure que la croissance s’accélère, des approches comme le sparse checkout (clone partiel) et les systèmes de fichiers virtuels (téléchargement dynamique des fichiers depuis le serveur selon le besoin) deviennent nécessaires
- Les grandes entreprises ont adapté cela en forkant Git ou en développant des systèmes séparés (Microsoft : son propre fork de Git, Meta : un fork de Mercurial, Google : Piper, etc.)
- Des outils de nouvelle génération comme Jujutsu méritent également d’être envisagés
- À petite échelle, Git s’utilise sans difficulté particulière, mais il faut garder en tête une stratégie d’extension pour la phase de croissance
- Il existe aussi un problème très concret : lorsque le code source inclut du code généré à partir d’un IDL (Interface Definition Language), la taille du dépôt peut croître de façon exponentielle
Système de build
- Bazel, Buck2, etc. sont des outils de build emblématiques des monorepos, capables de gérer de nombreux langages et des graphes de build complexes
- Ils sont puissants, mais impliquent aussi une forte complexité et une lourde charge d’exploitation
- Garder le build sur un langage unique simplifie énormément la vie, et les systèmes de build propres à chaque langage (par ex. Maven, Gradle, Cargo, Go) offrent eux aussi une grande capacité de montée en charge
- Le rôle central du système de build est de « construire efficacement la cible de build demandée (génération efficace d’artefacts) » et de « déterminer rapidement les cibles affectées par les fichiers modifiés »
- Pour cela, la notion de target determinator (outil de détermination des cibles) est nécessaire, et les écosystèmes Rust, Go, Bazel, etc. disposent déjà de diverses solutions
- L’exécution distante et le caching ne deviennent réellement nécessaires qu’à très grande échelle ; dans les entreprises ordinaires, la target determination est généralement plus utile en pratique
Tests
- Exécuter l’ensemble des tests à chaque fois est inefficace ; il faut donc un système qui teste uniquement la portée impactée par les changements
- Les flaky tests peuvent devenir un problème encore plus grave dans des systèmes de test à grande échelle
- Le système de test doit prévoir la relance automatique, l’évaluation automatique de la portée d’impact des tests et l’isolation des flaky tests
- Certains langages (par ex. Rust avec nextest, ou Java avec JUnit) proposent ces fonctions avancées nativement ou via des extensions
- Pour être efficace, le dispositif de test d’un monorepo doit être étroitement intégré au système de build
Intégration continue (CI)
- Le système de CI doit exécuter automatiquement les artefacts de build et les validations nécessaires en fonction des changements
- Les performances et l’efficacité du target determinator deviennent un élément clé du pipeline CI
- Le CI moderne utilise différentes stratégies, comme la Merge Queue, afin de trouver un équilibre entre le maintien de la qualité du code et l’optimisation de la vitesse de fusion
- Par exemple : faut-il exécuter toutes les validations pour chaque commit/PR, n’en sélectionner qu’une partie, ou traiter plusieurs PR par lots ?
- Il faut définir et concevoir soi-même les compromis entre Throughput (débit), Correctness (exactitude) et Tail latency (latence maximale)
- La gestion des fusions et l’amélioration de l’efficacité du CI dans les monorepos de grande taille restent encore aujourd’hui des défis sans solution parfaite
- Rust (bors), Chromium, Uber, etc. ont chacun adopté des stratégies différentes de fusion et de validation
Déploiement continu (CD)
- L’idée selon laquelle tous les changements d’un monorepo seraient déployés de manière atomique relève d’une illusion
- Une seule PR peut modifier en une fois les interfaces et implémentations de nombreux services, ainsi que leurs clients, mais le déploiement réel reste asynchrone, ce qui peut provoquer des problèmes au moment du déploiement
- Les changements qui cassent les contrats entre services peuvent entraîner des incidents graves lors du déploiement
- Une stratégie CD efficace pour un monorepo exige le bon rythme du système de déploiement, la validation des contrats entre services, ainsi que la capacité à détecter rapidement les problèmes et à y répondre
Conclusion
- Le monorepo est un outil puissant pour renforcer la cohérence organisationnelle et la culture d’ingénierie, mais il exige des investissements continus en ingénierie et en outillage
- À chaque étape, l’essentiel est de mettre en place l’automatisation, les outils et la culture en accord avec le principe de O(change)
- Il est important de faire évoluer les outils au fil de la croissance et de maintenir une gestion structurée qui reflète les objectifs et la culture de l’organisation
- Avec suffisamment de détermination, d’engagement et d’investissement durable, un monorepo finit par offrir une valeur à la hauteur des efforts consentis
4 commentaires
C’est un article vraiment formateur. Il ne suffit pas d’avoir des outils puissants, il faut aussi être prêt à créer les outils nécessaires quand le besoin se présente. Et si tout fonctionne bien, les bénéfices à en tirer sont nombreux.
À l’époque de mon master, mon directeur de recherche était allé déjeuner avec un ingénieur passé par Google et, visiblement après avoir entendu parler des monorepos, il nous avait proposé d’adopter nous aussi une gestion en monorepo à l’avenir ; j’avais eu du mal à l’en dissuader...
Les monorepos ont certes beaucoup d’avantages, mais dans notre labo, de par sa nature, nous devons souvent partager nos livrables avec des personnes extérieures, et si nous les avions gérés en monorepo, cela nous aurait particulièrement compliqué la tâche sur ce point. Avec plusieurs dépôts, il suffit simplement d’ajuster le niveau de visibilité pour chaque livrable.
La plupart des cas où l’on souffre avec un monorepo viennent, à mon avis, du fait qu’on a déjà découpé le projet beaucoup trop finement. On prend un projet qui aurait pu n’en former qu’un ou deux à l’origine, on le fragmente en une dizaine, puis on essaie de tout réunifier et gérer en monorepo : il faut alors utiliser des outils de gestion de monorepo et la complexité augmente aussi. Mieux vaut simplement fusionner le projet lui-même en un ou deux projets, et même s’il y en a plus de deux, on peut le gérer plus sereinement sans outil dédié, en séparant juste les répertoires et en le considérant comme le fait de mettre le tout dans un seul dépôt.
Avis Hacker News
En lisant ce fil, j’ai eu envie de partager une réflexion qui m’a rappelé les « complexity merchants » d’autrefois. Je ne suis absolument pas d’accord avec l’idée qu’un passage au monorepo impose des sacrifices techniques. Si on comprend la puissance d’un système de fichiers hiérarchique, on saisit la valeur d’un monorepo. Un CI/CD structuré autour d’un seul monorepo est bien plus clair qu’une configuration éparpillée un peu partout. Le cœur du monorepo, c’est que toute l’organisation peut faire des commits atomiques. Quand il faut coordonner beaucoup de développeurs, l’intérêt est écrasant. Un seul rebase et une seule grosse réunion suffisent. Même si les membres de l’équipe ne s’apprécient pas et ne collaborent pas volontiers, un monorepo joue aussi un grand rôle comme outil RH du point de vue de la gestion.
Ces derniers temps, les développeurs ont une tendance excessive à la séparation, aux microservices, à la multiplication des petits dépôts et à l’évitement extrême du monolithe. Cela accroît la complexité et transforme des problèmes d’organisation en problèmes techniques futurs. Beaucoup ne perçoivent même pas correctement les dépendances internes d’un système logiciel. Au point que le temps gaspillé, dans mon ancien poste, à mettre à jour des fichiers de schéma Protocol Buffers en devient incroyable. Heureusement, ce n’est pas le cas dans mon entreprise actuelle.
Suivre les commits sur plusieurs projets, c’est agréable à avoir, mais en pratique cela ne change pas grand-chose pour le suivi des dépendances ou le déclenchement des tests downstream. On peut très bien le faire avec de l’automatisation en multi-repo. Le monorepo aide, mais il n’est ni complet ni gratuit. Les déploiements et les builds ne sont pas atomiques. Quand un monorepo grossit, il faut sortir de git et passer à de nouveaux outils, ce qui représente un très gros chantier. Ce n’est pas un sujet sur lequel on peut parler à la légère sans expérience.
Les avantages du monorepo existent clairement, mais son coût de gestion est plus élevé que celui d’un polyrepo. Ce n’est pas parce qu’on est dans telle ou telle situation qu’un monorepo est automatiquement préférable. Pour plus de détails, voir cet article. Le rapport coût/bénéfice dépend du contexte.
Une règle empirique utile dans la conception des environnements de programmation est que plus on donne de pouvoir à une équipe, plus on crée de problèmes. Techniquement, les commits atomiques ne donnent pas plus de pouvoir, au contraire ils en donnent moins, mais ils permettent de travailler avec de mauvaises interfaces et deviennent donc un pouvoir qui crée des problèmes.
L’idée selon laquelle passer au monorepo rend les changements plus atomiques est un piège. [Citation de l’original : la plus grande illusion du monorepo est qu’il permet des commits atomiques sur toute la base de code. En réalité, il existe différents artefacts de déploiement ; même si l’on modifie d’un coup des services et des clients, le déploiement reste asynchrone. Avec plusieurs dépôts, il faut travailler sur plusieurs PR, ce qui rend le risque plus visible. Le CI d’un monorepo sert surtout à vérifier des contrats de service (jobs CI) et peut demander d’expliquer la raison d’un changement si nécessaire.]
Il existe deux types de monorepos dans la big tech. Le premier est le monorepo unique à l’échelle de toute l’entreprise, le « THE » monorepo mentionné dans l’article, qui nécessite un VCS/CI sur mesure et le support de 200 ingénieurs. Google, Meta et Uber fonctionnent ainsi. La souffrance nécessaire pour en arriver là dépasse l’imagination ; en général, on commence par des monorepos plus petits à l’échelle d’une équipe, puis on élargit progressivement. Chaque stack, langage ou équipe gère son propre environnement avec des outils comme Bazel, Turborepo ou Poetry, puis avec le temps tout cela fusionne dans un monorepo plus vaste. Mais dans les deux cas, cela exige des millions de dollars et des millions d’heures d’investissement, côté développeurs comme côté business, et au final cela reste maintenu grâce au soutien des développeurs qui ont traversé ce processus.
Après avoir travaillé dans une entreprise avec un grand monorepo, je le préfère de très loin. Un monorepo unique aide énormément à comprendre de manière transparente l’ensemble du système, comme le graphe des services ou la structure des appels de code. Dans le cas d’un polyrepo, les connaissances sont dispersées par équipe, la reprise de nouveau code est difficile et comprendre les archives de code ressemble à une exploration de labyrinthe. Un polyrepo donne l’impression de vieux messages Discord/Slack qu’on oublie. Si le monorepo coûte cher, le polyrepo coûte lui aussi, simplement autrement. Le monorepo, c’est un gigantesque herbivore sur un continent ; le polyrepo, ce sont des espèces variées perdues dans l’obscurité.
Dans mon entreprise actuelle, le backend est réparti sur environ 11 dépôts git, et une seule fonctionnalité nécessite 4 à 5 merge requests, ce qui est très pénible. Nous étudions l’adoption d’un monorepo pour regrouper plusieurs projets. Mais si l’on ne peut pas fusionner les dépôts, quelle est l’alternative au monorepo ?
Il n’existe toujours pas de système d’orchestration de monorepo à la fois simple et puissant, indépendant du langage. Bazel est complexe et difficile à apprendre, même si la documentation s’est beaucoup améliorée récemment. Il existe d’autres options comme Buck, NX ou Pants, mais chacune a ses particularités, et le support web reste notamment limité. La plupart des CI ne prennent pas correctement ces outils en charge, donc leur configuration est délicate. À noter que Rush de Microsoft offre la meilleure expérience ; je le recommande particulièrement pour les monorepos frontend/NodeJS : site officiel de Rush.
En pratique, la plupart des monorepos ne grandissent pas jusqu’à la taille de Google, Uber ou Meta. Le nombre de services varie d’une entreprise à l’autre, et même s’il y en a une centaine, il n’y a généralement pas de problème d’échelle côté VCS et les tags LSP tournent très bien sur un laptop. Même faire tourner tous les tests en CI de manière brute passe souvent sans souci. Conclusion : toutes les entreprises n’ont pas besoin d’être à l’échelle de Google.
Dans mon entreprise actuelle, nous construisons des monorepos par stack de langage. C’est un compromis assez satisfaisant.
Un point souvent absent des discussions monorepo vs multi-repo est l’« anti-loi de Conway ». La structure des dépôts influence la structure de l’organisation et la manière de résoudre les problèmes. Le monorepo pousse l’équipe d’infrastructure commune à faire un travail héroïque ; comme toucher aux zones partagées augmente les risques de casse, même développer une seule fonctionnalité devient plus difficile. En multi-repo, il faut plusieurs PR entre équipes, de la coordination et de la politique interne, mais cela permet aussi à un plus grand nombre de développeurs de se répartir les rôles.
Même dans un monorepo, si un changement touche profondément le centre du système, on peut l’appliquer en plusieurs étapes. Il faut alors gérer plusieurs PR, de la coordination et des questions politiques, mais le monorepo permet justement de voir plus clairement l’état du rollout.
En polyrepo, il est bien plus fréquent que des changements dans des zones communes ne soient pas répercutés dans les dépôts downstream, que chaque dépôt reste figé sur une version différente, et qu’on souffre d’éléments non mis à jour depuis des années.
Je me demande si l’hypothèse selon laquelle une organisation choisit d’abord une orientation via la structure de ses dépôts, puis laisse les choix techniques suivre, est juste. En réalité, une philosophie d’organisation plus fondamentale — fragmentation contre partage — précède le choix d’une structure de repo précise. Et même si l’orientation change, on peut encore ajuster la manière de gérer le code. Un multi-repo peut tout à fait permettre aux ingénieurs d’accéder à presque tout le code, et un monorepo peut aussi imposer une forte isolation ainsi que des règles séparées pour le CI ou la gestion des déploiements.
Dans un monorepo, les changements faciles entre projets sont bien plus souvent tentés ; en polyrepo, c’est souvent tellement pénible qu’on n’essaie même pas.
D’après mon expérience dans les grandes entreprises tech, la gestion du système de build nécessite carrément une équipe dédiée. Les grands monorepos reposent sur des systèmes de fichiers virtuels qui téléchargent les fichiers source à la demande. Un point non mentionné dans l’article est que presque tout le développement se fait sur des serveurs de développement dans le datacenter, avec des environnements à 50 à 100 cœurs ou des conteneurs à la demande mis à jour en continu avec les derniers commits. L’IDE est intégré au dev server, et le pré-provisionnement / l’autoconfiguration par langage ou par service est automatisé via chef/ansible. Il est très rare de faire directement du développement de gros monorepo sur laptop, sauf cas particuliers comme le mobile ou les apps Mac.
J’imagine qu’on a probablement travaillé dans la même équipe build. Que l’environnement de développement monorepo soit local ou distant, la reproductibilité est encore plus importante. Avec un dev server distant imageable, c’est plus simple et plus fiable.
J’ai aussi connu des environnements de développement en datacenter dans de petites équipes. Vu le prix et la densité du matériel actuel, monter son propre rack pour faire tourner à la demande les outils de dev/staging/test est bien plus rationnel. Quand on partage un environnement de développement proche de la production, l’approche monorepo paraît très différente. Mais les petites et moyennes entreprises n’ont pas les moyens d’investir dans un système de build, et n’atteignent pas non plus les problèmes propres à ces énormes systèmes de build ; avec une équipe d’au moins 10 à 20 personnes et même un produit très complexe, la maintenance peut n’être qu’un travail à temps partiel.
Récit d’une petite équipe chez Molnett (serverless cloud) ayant obtenu une énorme efficacité grâce à un monorepo basé sur Bazel, avec 1,5 ETP. Avec Tilt+Bazel+Kind, toute la plateforme ainsi que l’opérateur Kubernetes peuvent tourner sur un laptop, avec support Mac/Linux. Même un OS basé sur Bottlerocket et Firecracker peut être validé localement. Une couche d’outillage garantit à tous les développeurs les mêmes versions de go/kubectl, sans installation locale nécessaire. Cela demande des efforts de gestion, mais c’est possible grâce à un ancien membre de l’équipe SRE de Google. Ils ne veulent plus travailler autrement. (Langages principaux : Golang, Bash, Rust)
À 1,5 personne, le dépôt unique va de soi. Mon expérience avec Bazel a été très mauvaise, mais sur de gros projets cela peut valoir le coup. Avec moins de 2 personnes, Kind+Tilt suffisent probablement déjà. La couche d’outillage aussi, Go règle déjà en partie le problème avec go.mod. Pour kubectl, on peut faire quelque chose de similaire. Il faut aussi réfléchir au niveau de salaire d’un ex-Googler. J’espère que le coût de maintenance de Bazel continuera de valoir la peine.
Dans notre entreprise, nous déployons avec des services basés sur systemd et des playbooks ansible, et avec tmuxinator nous lançons automatiquement d’un seul coup en mode dev tous les services, du backend à la base de données, au moteur de recherche et au frontend. Une seule commande
tmuxinatorà la racine, et tout l’environnement de dev est prêt. Un monorepo unique est incomparablement plus pratique qu’avant.Situation similaire ici, avec un retour d’expérience sur les effets maximisés de l’adoption de Bazel. Grâce à la couche d’outillage, on peut garder un environnement de développement cohérent. Il faut toutefois utiliser directement
bazel run, et je me demande s’il existe une meilleure manière d’automatiser cela. J’aimerais savoir comment cela fonctionne.À deux personnes, le schéma microservices/K8s lui-même relève de l’over-engineering. À cette taille d’équipe, n’importe quelle approche fonctionne. Avant, on utilisait Dropbox/SVN/MS VCS ou autre, et tout marchait quand même (même si ce n’était pas pratique). À cette échelle, tout le monde peut garder en tête l’ensemble du processus. D’expérience, ni les outils complexes ni l’infrastructure ne sont le facteur décisif du succès.
Retour d’expérience d’un freelance qui a mis en place un monorepo trois fois en quatre ans dans différentes entreprises. Comme il travaille seulement sur le frontend et dans l’écosystème JavaScript/TypeScript, c’est malgré tout plus facile à gérer. Un bon monorepo fonctionne en réalité comme un polyrepo en interne : chaque projet peut être développé, déployé et hébergé indépendamment, tout en coexistant dans une même base de code, avec la possibilité de partager librement des composants communs (UI, etc.) et de garantir une expérience visuelle cohérente. Comme guide pratique, il recommande cette ressource.
Au fond, tout dépend du contexte. Dans notre entreprise, nous gérons environ 40 dépôts git avec des CI séparés ; après build/test/packaging, nous créons au final une image de système de fichiers intégrée pour les tests d’intégration. Les composants communiquent entre eux via des messages Flatbuffers, et Flatbuffers lui-même est géré comme submodule. Gérer les dépendances downstream est difficile, mais on obtient une certaine flexibilité via le progressive enhancement. Dans un cas comme celui-ci, il est déjà difficile de dire s’il s’agit d’un multi-repo ou d’un monorepo rempli de submodules. On ignore si passer au monorepo apporterait un réel bénéfice. Au final, tout est affaire de compromis et du type d’inconfort qu’on accepte.
Retour de l’auteur de ce blog sur les outils de monorepo. Les gens insistent surtout sur les avantages du monorepo, mais dans la réalité, la complexité d’une exploitation réussie est en grande partie absorbée en coulisses par les équipes devops/devtools. Il faut donc l’adopter avec prudence, mais quand c’est bien mis en place, cela peut apporter une vraie valeur.
Une expérience de monorepo bien géré est tellement agréable qu’on n’a plus envie de revenir à d’autres workflows. En revanche, une approche improvisée du type « nous aussi, faisons un monorepo » est un cauchemar. Si quelqu’un vendait un environnement et des outils de monorepo prêts à l’emploi, il y aurait sans doute une vraie opportunité business.
J’ai aussi constaté que, dans les grandes organisations, un monorepo peut au contraire limiter à l’extrême les dépendances entre équipes et réduire la réutilisation du code. Quand l’équipe d’une bibliothèque veut faire évoluer quelque chose, tous les utilisateurs en aval doivent être mis à jour, mais des équipes l’utilisent parfois de manière imprévue, ce qui complique tout énormément (loi de Hyrum). Au final, les grandes entreprises aboutissent à des copier-coller internes, des forks, des contrôles d’accès stricts et des validations de changement lentes.
Quand on conçoit une bibliothèque destinée à un usage général, il faut être prudent dans la conception de l’API. Si possible, il vaut mieux ne pas la modifier ; si un changement est nécessaire, il faut soit planifier sérieusement une évolution de grande ampleur, soit introduire une nouvelle fonction et déprécier l’ancienne. Pour un petit morceau de code, le copier-coller peut aussi être acceptable.
Malgré cela, l’avantage du monorepo reste qu’on peut retrouver facilement tous les points d’usage et, si nécessaire, les modifier ou les corriger de manière atomique.
Tout logiciel doit tenir compte de ses dépendances, et le monorepo augmente au contraire la capacité d’une bibliothèque et de ses utilisateurs à se faire évoluer mutuellement.
Dans un monorepo, comme il est facile d’adapter le code à son propre contexte, la probabilité de réutilisation du code est plus élevée qu’en polyrepo.