Du traducteur: Bonjour, Habr! Oui, c'est un autre article sur les avantages et les inconvénients des monorepositoires. J'allais écrire mon article sur la façon dont nous utilisons le mono-référentiel, comment nous sommes passés de maven à bazel et ce qui en est résulté. Mais pendant que j'y réfléchissais, un excellent article du développeur de Lyft est sorti, que j'ai décidé de traduire pour vous. Je promets de publier mes ajouts à l'article, ainsi que l'expérience avec bazel en tant que suite.
Nous sommes dans la nouvelle année 2019, et je suis prêt pour une autre discussion sur les avantages (ou leur absence) du stockage de tout le code source de l'organisation dans le «Monorepository». Pour ceux d'entre vous qui ne connaissent pas cette approche, l'idée est de stocker tout le code source dans un seul référentiel du système de contrôle de version. Une alternative, bien sûr, consiste à stocker le code source dans plusieurs référentiels indépendants, généralement en les divisant le long de la frontière des services / applications / bibliothèques.
Dans cet article, j'appellerai cette approche «polyrepository».
Certains géants de l'informatique utilisent des mono-référentiels, dont Google, Facebook, Twitter et autres. Bien sûr, si de telles sociétés réputées utilisent des mono-référentiels, les avantages de cette approche devraient être énormes, et nous devrions tous faire de même, non? Non! Comme le dit le titre de l'article: "Veuillez ne pas utiliser le mono-dépôt!" Pourquoi? Parce
qu'à grande échelle, le monorepositaire résoudra tous les mêmes problèmes que le polyrepositaire résout, mais en même temps vous provoquant à la forte cohérence de votre code et nécessitant des efforts incroyables pour augmenter l'évolutivité de votre système de contrôle de version .
Ainsi, à moyen et long terme, le mono-référentiel n'apporte aucun avantage organisationnel, alors qu'il laisse les meilleurs ingénieurs de l'entreprise avec un syndrome post-traumatique (se manifestant sous forme de bavardage et de marmonnements incohérents sur les performances git).
Digression courte: qu'est-ce que j'entends par "à grande échelle"? Il n'y a pas de réponse unique à cette question, mais parce que Je suis sûr que vous me posez des questions à ce sujet, disons qu'il y a environ 100 développeurs qui écrivent du code à temps plein.
Avantages théoriques d'un monoréférentiel et pourquoi ils ne peuvent être atteints sans outils utilisés pour les polyrezitoires (ou faux)
Avantage théorique 1: collaboration et partage de code plus faciles
Les partisans des mono-référentiels affirment que lorsque tout le code est dans le même référentiel, la probabilité de duplication de code est moindre, et il est plus probable que différentes équipes travailleront ensemble sur une infrastructure commune.
Voici l'amère vérité sur les mono-référentiels même de taille moyenne (et cela sonnera constamment dans cette section): il devient rapidement impossible pour un développeur de conserver tout le code du référentiel sur son poste de travail ou de rechercher la base de code entière à l'aide d'utilitaires comme grep. Par conséquent, tout mono-référentiel qui souhaite évoluer doit fournir 2 choses:
1) quelque chose comme un système de fichiers virtuel qui vous permet de stocker localement seulement une partie du code. Cela peut être réalisé en utilisant un système de fichiers propriétaire comme
Perforce , qui prend en charge ce mode nativement, en utilisant l'outil
G3 interne de Google ou le
GVFS de Microsoft.
2) des outils sophistiqués en tant que service (en tant que service) pour l'indexation / la recherche / l'affichage du code source. Parce que aucun des développeurs ne va stocker tout le code source sur leur poste de travail dans un état de recherche, il devient essentiel de pouvoir effectuer une telle recherche dans toute la base de code.
Étant donné que le développeur n'aura accès qu'à une petite partie du code source à un moment donné, existe-t-il au moins une certaine différence entre le téléchargement d'une partie du mono-référentiel ou le téléchargement de plusieurs référentiels indépendants?
Il n'y a aucune différence .
Dans le cadre de l'indexation / recherche / navigation et du code similaire, un tel outil hypothétique peut facilement rechercher plusieurs référentiels et combiner le résultat. En fait, c'est exactement comment fonctionne la recherche sur GitHub, ainsi que des outils de recherche et d'indexation plus sophistiqués comme
Sourcegraph .
Ainsi, du point de vue du travail collaboratif sur le code à grande échelle, les développeurs sont en tout cas contraints de travailler uniquement avec une partie de la base de code et d'utiliser des outils de niveau supérieur. Peu importe que le code soit stocké dans un mono-référentiel ou dans plusieurs référentiels indépendants, le problème est résolu de la même manière, et l'
efficacité de travailler ensemble sur le code dépend uniquement de la culture d'ingénierie, et non de la façon dont les codes source sont stockés .
Avantage théorique 2: un assemblage / pas de gestion des dépendances
L'argument suivant, généralement cité par les partisans des mono-référentiels, est que le stockage de tout le code dans un seul mono-référentiel vous prive de la nécessité de gérer les dépendances car tout le code est collecté en même temps. C'est un mensonge! À grande échelle, il n'y a tout simplement aucun moyen de reconstruire tout le code source et d'exécuter tous les tests automatisés chaque fois que quelqu'un valide des modifications du système de contrôle de version (ou, plus important encore, plus souvent, sur le serveur CI lorsqu'une nouvelle branche ou demande d'extraction est créée). Pour résoudre ce problème, tous les grands référentiels mono utilisent leur système de construction sophistiqué (par exemple
Bazel / Blaze de Google ou
Buck de Facebook), qui est conçu pour surveiller les modifications et leurs blocs dépendants et créer un graphique de dépendance du code source. Ce graphique vous permet d'organiser une mise en cache efficace des résultats et des tests d'assemblage, de sorte que seules les modifications et leurs dépendances nécessitent un réassemblage et des tests.
De plus, puisque le code collecté devrait finalement être déployé et, comme vous le savez, tous les logiciels ne peuvent pas être déployés en même temps, il est important que tous les artefacts d'assemblage soient contrôlés, afin que les artefacts soient refaits si nécessaire. En substance, cela signifie que même dans le monde des mono-référentiels, plusieurs versions du code peuvent exister en même temps dans la nature et doivent être soigneusement surveillées et coordonnées.
Les partisans des mono-référentiels feront également valoir que même en tenant compte de la nécessité de suivre les assemblages / dépendances, cela offre toujours un avantage indéniable, car un seul commit décrit l'état complet du monde entier. Je dirais que cet avantage est plutôt controversé, étant donné que le graphique de dépendance existe déjà, et il semble être une tâche plutôt triviale d'inclure l'identifiant de validation pour chaque référentiel indépendant dans le cadre de ce graphique, et en fait Bazel peut facilement travailler avec plusieurs référentiels indépendants ainsi qu'avec un seul. mono-référentiel, soustrayant le niveau sous-jacent du développeur. De plus, il est facile d'implémenter de tels outils de refactorisation automatisés qui mettent à jour automatiquement les versions des bibliothèques dépendantes dans plusieurs référentiels indépendants à la fois, nivelant la différence entre le mono-référentiel et le polyrepositaire dans cette partie (plus à ce sujet plus tard).
Le résultat final est que les réalités de l'assemblage / déploiement à grande échelle sont pour la plupart les mêmes pour les mono-référentiels et les poly-référentiels.
Il n'y a pas de différence pour les outils, cela ne devrait pas être pour les développeurs qui écrivent du code .
Avantage théorique 3: le refactoring de code est un simple commit atomique
Enfin, la dernière vertu mentionnée par les partisans des mono-référentiels est le fait qu'un référentiel simplifie la refactorisation de code en raison de la facilité de recherche, et l'idée qu'un seul commit peut s'étendre sur l'ensemble du référentiel. Ce n'est pas vrai pour plusieurs raisons:
1) comme décrit ci-dessus, à grande échelle, le développeur ne sera pas en mesure de modifier ou de rechercher la base de code entière sur sa machine locale. Ainsi, l'idée que n'importe qui peut facilement cloner tout son référentiel pour lui-même et simplement faire grep / replace n'est pas si facile à mettre en pratique.
2) même si nous supposons qu'avec l'aide d'un système de fichiers virtuel complexe, un développeur peut cloner et modifier l'intégralité de la base de code, à quelle fréquence cela se produira-t-il? Je ne parle pas de corriger un bogue dans la mise en œuvre d'une bibliothèque partagée, car cette situation est gérée de la même manière dans le cas d'un référentiel unique et dans le cas d'un multi-référentiel (en supposant un système de construction / déploiement similaire, comme décrit ci-dessus). Je parle de changer l'API de la bibliothèque, qui sera suivie de nombreuses erreurs de compilation aux endroits où cette bibliothèque est appelée. Dans une très grande base de code, il est
presque impossible de modifier l'API de base, qui sera prévisualisée par toutes les équipes impliquées avant que les conflits de fusion ne vous obligent à recommencer le processus . Le développeur a 2 possibilités réelles: il peut abandonner et trouver une solution de contournement pour le problème avec l'API (en pratique, cela se produit plus souvent que nous ne le souhaiterions tous), ou il peut dévier l'API existante, écrire une nouvelle API et ensuite se lancer dans la longue et mettre à jour péniblement tous les appels à l'ancienne API dans la base de code. En tout cas,
c'est absolument le même processus qu'avec le polyrepository .
3) dans un monde axé sur les services, les applications se composent de nombreux composants à couplage lâche qui interagissent les uns avec les autres à l'aide d'un certain type d'API bien décrite. Les grandes organisations passeront tôt ou tard à l'utilisation de l'IDL (langage de description d'interface), comme Thrift ou Protobuf, qui vous permet de créer des API de type sécurisé et d'apporter des modifications rétrocompatibles. Comme décrit dans la section précédente sur l'assemblage / déploiement, le
code ne peut pas être déployé simultanément . Il peut être déployé sur une période de temps: des heures, des jours, voire des mois. Par conséquent, les développeurs doivent réfléchir à la compatibilité descendante de leurs modifications. Telle est la réalité du développement de logiciels modernes, que beaucoup voudraient ignorer, mais ne peuvent pas. Par conséquent, en ce qui concerne les services (par opposition aux bibliothèques d'API), les développeurs doivent utiliser l'une des deux approches décrites ci-dessus (ne changez pas l'API ou ne passez pas par le cycle de dépréciation) et
c'est absolument la même chose pour le monorepositaire et le polyrepositaire .
En parlant de refactoring à grande base de code, de nombreuses grandes organisations développent leurs propres outils de refactoring automatisés, tels que
fastmod , récemment publié par Facebook. Comme toujours, cet outil pourrait facilement fonctionner avec un référentiel ou plusieurs référentiels indépendants. Lyft a un outil appelé "refactorator" qui fait exactement cela. Il fonctionne comme fastmod, mais il automatise les modifications dans plusieurs de nos référentiels, y compris la création de demandes d'extraction, le suivi des statuts des avis, etc.
Inconvénients uniques des monorepositoires
Dans la section précédente, j'ai énuméré tous les avantages théoriques qu'un monorepositaire offre, et j'ai noté que pour en tirer parti, il est nécessaire de créer des outils incroyablement complexes qui ne différeront pas de ceux des polyrepositoires. Dans cette section, je mentionnerai 2 inconvénients uniques des mono-référentiels.
Inconvénient 1: forte connectivité et logiciels open source
Sur le plan organisationnel, un monorepositaire provoque la création de logiciels étroitement couplés et fragiles. Cela donne aux développeurs le sentiment qu'ils peuvent facilement corriger les erreurs dans les abstractions, bien qu'en réalité ils ne le puissent pas en raison du processus d'assemblage / déploiement instable et des facteurs humains / organisationnels / culturels qui surviennent lorsqu'ils essaient d'apporter des modifications immédiatement dans la base de code.
La structure de code dans les polyrepositories représente des frontières claires et transparentes entre les équipes / projets / abstractions / propriétaires de code et oblige le développeur à examiner attentivement l'interface d'interaction. C'est un avantage subtil, mais très important: il fait réfléchir les développeurs de manière plus large et à plus long terme. De plus, l'utilisation de plusieurs référentiels ne signifie pas que les développeurs ne peuvent pas dépasser les limites du référentiel. Que cela se produise ou non, cela ne dépend que de la culture de développement et non de l'utilisation d'un monorepositaire ou d'un polyrepositaire.
Une liaison forte a également de graves conséquences sur l'ouverture de son code source. Si une entreprise souhaite créer ou consommer des logiciels open source, l'utilisation de multi-référentiels est indispensable. Les distorsions qui se produisent lorsqu'une entreprise essaie de présenter son projet en open source à partir de son mono-référentiel (importation / exportation de codes source, tracker de bogues public / privé, couches supplémentaires pour résumer la différence dans les bibliothèques standard, etc.) ne conduisent pas à une collaboration productive et bâtir une communauté et créer des frais généraux importants.
Défaut 2: évolutivité du système de contrôle de version
Faire évoluer un système de contrôle de version pour des centaines de développeurs, des centaines de millions de lignes de code et un énorme flux de commits est une tâche monumentale. Le mono-référentiel Twitter, créé il y a 5 ans (basé sur git), était l'un des projets les plus inutiles que j'ai vu dans ma carrière. L'exécution d'une commande simple comme
git status
pris
quelques minutes . Si la copie locale du référentiel était trop ancienne, la mise à jour pouvait prendre des
heures (à l'époque, il était même pratique d'envoyer des disques durs avec une copie du référentiel à des employés distants avec la dernière version du code). Je m'en souviens non pas pour se moquer des développeurs Twitter, mais pour illustrer la complexité de ce problème. Je peux dire que 5 ans plus tard, les performances du mono-référentiel Twitter sont encore loin de celles que les développeurs de l'équipe Tilling aimeraient voir, et ce n'est pas parce qu'ils ont fait de gros efforts.
Bien sûr, au cours des 5 dernières années, un certain développement a eu lieu dans ce domaine.
Git VFS de Microsoft, qui est utilisé pour développer Windows, a conduit à l'émergence d'un véritable système de fichiers virtuel pour git, que j'ai décrit ci-dessus comme une condition préalable à la mise à l'échelle d'un système de contrôle de version (et avec l'achat de Microsoft Github, il semble que ce niveau de mise à l'échelle trouvera son application dans les fonctionnalités que GiHub propose à ses entreprises clientes). Et bien sûr, Google et Facebook continuent d'investir d'énormes ressources dans leurs systèmes internes afin qu'ils continuent de fonctionner, bien que presque rien de tout cela ne soit accessible au public.
Alors, pourquoi devez-vous généralement résoudre ces problèmes avec la mise à l'échelle du système de contrôle de version, si, comme décrit dans la section précédente, la boîte à outils doit être exactement la même que pour le multi-référentiel? Il n'y a aucune raison raisonnable à cela.
Conclusion
Comme cela se produit souvent dans le développement de logiciels, nous considérons les sociétés de logiciels les plus performantes comme un exemple et essayons d'emprunter leurs meilleures pratiques sans comprendre exactement ce qui a conduit ces sociétés au succès. Les monorepositoires, à mon avis, sont un exemple typique d'un tel cas. Google, Facebook et Twitter ont investi une énorme quantité de ressources dans leurs systèmes de stockage de code uniquement pour proposer une solution qui est essentiellement la
même que celle requise pour un multi-référentiel, mais provoque une forte liaison et nécessite un investissement énorme dans la mise à l'échelle du contrôle de version .
En fait, à grande échelle, comment une entreprise travaille avec le code, la collaboration, la liaison forte, etc.
dépend directement de la culture d'ingénierie et du leadership, et n'a pas à voir avec l'utilisation d'un monorepositaire ou d'un polypositaire . Les deux solutions se ressemblent pour le développeur. Alors pourquoi utiliser un monorepositaire?
S'il vous plait, ne le faites pas