Bonjour à tous!
Donc, une nouvelle partie de l'holivar promis sur les monorepositoires. Dans la première partie, nous avons discuté de la traduction d'un article par un ingénieur respecté de Lyft (et précédemment Twitter) sur quels sont les inconvénients des mono-référentiels et pourquoi ils nivellent presque tous les avantages de cette approche. Personnellement, je suis largement d'accord avec les arguments présentés dans l'article d'origine. Mais, comme promis, pour mettre un terme à cette discussion, je voudrais exprimer quelques points supplémentaires, à mon avis encore plus importants et plus pratiques.
Je vais vous parler un peu de moi - j'ai travaillé à la fois dans de petits projets et dans des projets relativement importants, j'ai utilisé des polyrepositoires dans un projet avec plus de 100 microservices (et SLA à 99,999%). En ce moment, je suis engagé dans la traduction d'un petit mono-dépôt (en fait pas, juste le front js + backend java) de maven en bazel. N'a pas fonctionné sur Google, Facebook, Twitter, c.-à-d. Je n'ai pas eu le plaisir d'utiliser un mono-référentiel correctement configuré et réglé.
Alors, pour commencer, qu'est-ce qu'un monorepositaire? Les commentaires sur la traduction de l'article original ont montré que beaucoup pensent qu'un mono-référentiel, c'est quand les 5 développeurs de l'entreprise travaillent sur un seul référentiel et y stockent le frontend et le backend ensemble. Bien sûr, ce n'est pas le cas. Un mono-référentiel est un moyen de stocker tous les projets de l'entreprise, les bibliothèques, les outils de construction, les plug-ins IDE, les scripts de déploiement et tout le reste dans un grand référentiel. Les détails ici sont
trunkbaseddevelopment.com .
Comment s'appelle l'approche lorsque l'entreprise est petite et qu'elle n'a tout simplement pas autant de projets, modules, composants? C'est aussi un monorepositaire, seulement petit.
Naturellement, l'article original dit que tous les problèmes décrits commencent à apparaître à une certaine échelle. Par conséquent, ceux qui écrivent que leur mono-référentiel de 1.5 digger fonctionne parfaitement ont certainement tout à fait raison.
Donc, le premier fait que je voudrais corriger: un
monorepositaire est un bon début pour votre nouveau projet . En mettant tout le code dans un même tas, au début, vous n'aurez qu'un seul avantage, car la prise en charge de plusieurs référentiels ajoutera certainement un peu de surcharge.
Quel est donc le problème? Et le problème, comme indiqué dans l'article d'origine, commence à une certaine échelle. Et surtout, ne manquez pas le moment où une telle échelle est déjà arrivée.
Par conséquent, je suis enclin à affirmer que, essentiellement, les problèmes qui se posent ne sont pas les problèmes de l'approche «mettre tout votre code dans un seul tas», mais ce sont des problèmes de référentiels de code source simplement volumineux. C'est-à-dire en supposant que vous avez utilisé des polyrepositoires pour différents services / composants, et que l'un de ces services est devenu si grand (quelle taille, nous en discuterons un peu plus tard), vous obtiendrez très probablement exactement les mêmes problèmes, mais aussi sans les avantages des mono-référentiels (s'ils Bien sûr qu'il y en a).
Alors, quelle devrait être la taille du référentiel pour commencer à être considéré comme problématique?
Il y a certainement 2 indicateurs dont cela dépend - la quantité de code et le nombre de développeurs travaillant avec ce code. Si votre projet contient des téraoctets de code, mais que 1-2 personnes y travaillent, il est fort probable qu'ils ne remarqueront presque pas de problèmes (enfin, ou du moins il sera plus facile de ne rien faire, même s'ils le remarquent :)
Comment déterminer qu'il est temps de réfléchir à la façon d'améliorer votre référentiel? Bien sûr, c'est un indicateur subjectif, vos développeurs commenceront probablement à se plaindre que quelque chose ne leur convient pas. Mais le problème est qu'il peut être trop tard pour changer quelque chose. Permettez-moi de vous donner quelques chiffres personnels: si le clonage de votre référentiel prend plus de 10 minutes, si la construction d'un projet prend plus de 20-30 minutes, si le nombre de développeurs dépasse 50, et ainsi de suite.
Un fait intéressant de la pratique personnelle:J'ai travaillé sur un assez gros monolithe au sein d'une équipe d'une cinquantaine de développeurs, répartis en plusieurs petites équipes. Le développement a été effectué dans des brunchs de fonctionnalités, et la fusion a eu lieu juste avant le gel des fonctionnalités. Une fois, j'ai passé 3 jours sur la fusion de notre branche d'équipe après que 6 autres équipes se sont figées devant moi.
Passons maintenant en revue la liste de ces problèmes qui surviennent dans les grands référentiels (certains d'entre eux ont été mentionnés dans l'article d'origine, d'autres ne le sont pas).
1) Temps de téléchargement du référentiel
D'une part, on peut dire qu'il s'agit d'une opération ponctuelle que le développeur effectue lors de la configuration initiale de son poste de travail. Personnellement, j'ai souvent des situations où je veux cloner un projet dans un dossier voisin, creuser plus profondément dans celui-ci, puis le supprimer. Cependant, si le clonage prend plus de 10 à 20 minutes, cela ne sera pas aussi pratique.
Mais en plus, n'oubliez pas qu'avant d'assembler le projet sur le serveur CI, vous devez cloner le référentiel de chaque agent de build. Et ici, vous commencez à comprendre comment gagner du temps, car si chaque assemblage prend 10 à 20 minutes de plus et que le résultat de l'assemblage apparaît 10 à 20 minutes plus tard, cela ne conviendra à personne. Ainsi, le référentiel commence à apparaître dans les images des machines virtuelles à partir desquelles les agents sont déployés, une complexité supplémentaire et des coûts supplémentaires pour la prise en charge de cette solution apparaissent.
2) Construire le temps
C'est un point assez évident qui a été discuté à plusieurs reprises. En fait, si vous avez beaucoup de codes sources, l'assemblage prendra en tout cas un temps considérable. Une situation familière survient lorsque, après avoir modifié une ligne de code, vous devez attendre une demi-heure jusqu'à ce que les modifications soient réassemblées et testées. En fait, il n'y a qu'une seule issue: utiliser un système de build basé sur les résultats de mise en cache et les builds incrémentiels.
Il n'y a pas tellement d'options ici - malgré le fait que les capacités de mise en cache ont été ajoutées au même gradle (malheureusement, je ne les ai pas utilisées dans la pratique), elles n'apportent pas d'avantages pratiques car les systèmes de construction traditionnels n'ont pas de résultats reproductibles (versions reproductibles). C'est-à-dire en raison des effets secondaires de la version précédente, de toute façon, à un moment donné, il sera nécessaire d'appeler le nettoyage du cache (l'approche standard de
maven clean build
). Par conséquent, il ne reste que l'option d'utiliser Bazel / Buck / Pants et d'autres comme eux. Pourquoi ce n'est pas très bon, nous en discuterons un peu plus tard.
3) Indexation IDE
Mon projet actuel est indexé dans Intellij IDEA pendant 30 à 40 minutes. Et le vôtre? Bien sûr, vous ne pouvez ouvrir qu'une partie du projet ou exclure tous les modules inutiles de l'indexation, mais ... Le problème est que la réindexation se produit chaque fois que vous passez d'une branche à une autre. C'est pourquoi j'aime cloner un projet dans un répertoire voisin. Certaines personnes commencent à mettre en cache le cache IDE :)
<Image DiCaprio avec les yeux plissés>
4) Créer des journaux
Quel serveur CI utilisez-vous? Fournit-il une interface pratique pour afficher et parcourir plusieurs gigaoctets de journaux de build? Malheureusement, le mien n'est pas :(
5) Historique des commits
Aimez-vous regarder l'historique des commit? J'adore, surtout dans un outil avec une interface graphique (je perçois mieux l'information visuellement, ne gronde pas :).
Voici à quoi ressemble l'historique des validations dans mon référentiel Vous l'aimez? Est-ce pratique? Personnellement, je n'en ai pas!
6) Tests brisés
Que se passe-t-il si quelqu'un a pu exécuter des tests cassés / du code non compilable dans le maître? Vous direz certainement que votre CI ne vous permet pas de le faire. Qu'en est-il des tests instables que l'auteur réussit, et personne d'autre? Imaginez maintenant que ce code se propage aux machines de 300 développeurs, et qu'aucun d'eux ne peut assembler un projet? Que faire dans une telle situation? Attendez que l'auteur remarque et corrige? Correct pour lui? Annuler les modifications? Bien sûr, idéalement, il vaut la peine de n'engager que du bon code et d'écrire immédiatement sans bugs. Un tel problème ne se posera alors pas.
(pour ceux qui n'ont pas compris les indices dans le réservoir, le discours sur l'effet négatif si cela se produit dans le référentiel avec 10 développeurs et dans le référentiel avec 300 sera légèrement différent)
7) Fusionner le bot
Avez-vous déjà entendu parler d'une telle chose? Savez-vous pourquoi vous en avez besoin? Vous allez rire, mais c'est un autre outil qui n'aurait pas dû exister :) Imaginez que le temps de construction de votre projet soit de 30 minutes. Et 100 développeurs travaillent sur votre projet. Supposons que chacun d'eux envoie 1 commit par jour. Imaginez maintenant un CI honnête, qui vous permet de fusionner les modifications apportées au maître uniquement après qu'elles ont été appliquées à la dernière validation du maître (rebase).
Attention, la question est: combien d'heures devrait-il y avoir dans une journée pour qu'un serveur CI aussi honnête étrangle les changements de tous les développeurs? La bonne réponse est 50. Ceux qui ont répondu correctement peuvent prendre une carotte sur une étagère. Eh bien, ou imaginez comment vous venez de couper votre commit sur le tout dernier commit sur le master, que vous avez commencé l'assemblage, et quand il a été terminé, le master a déjà passé 20 commits. Encore une fois?
Ainsi, le bot de fusion ou la file d'attente de fusion est un service qui automatise le processus de rebasage de toutes les demandes de fusion pour un nouveau maître, l'exécution de tests et la fusion elle-même, et peut également combiner des validations en lots et les tester ensemble. Chose très pratique. Voir
mergify.io ,
k8s test-infra Prow de Google,
bors-ng , etc. (je promets d'écrire plus à ce sujet à l'avenir)
Maintenant pour des problèmes moins techniques:
8) Utilisation d'un seul outil de construction
Honnêtement, c'est toujours un mystère pour moi pourquoi assembler le mono-référentiel entier en utilisant un système de construction commun. Pourquoi ne pas construire javascript avec Yarn, java avec gradle, Scala avec sbt, etc.? Si quelqu'un connaît la réponse à cette question (ne devine pas ou ne suggère pas, à savoir sait), écrivez dans les commentaires.
Bien sûr, il semble évident que l'utilisation d'un système de build est meilleure que plusieurs systèmes différents. Mais ils comprennent toujours que toute chose universelle est évidemment pire qu'une chose spécialisée, car il n'a probablement qu'un sous-ensemble des fonctions de tous les spécialistes. Mais pire encore, différents langages de programmation peuvent avoir des paradigmes différents en termes d'assemblage, de gestion des dépendances, etc., qui seront très difficiles à encapsuler dans un seul wrapper commun. Je ne veux pas entrer dans les détails, je vais donner un exemple sur bazel (voir les détails dans un article séparé) - nous avons trouvé 5 implémentations indépendantes des règles d'assemblage javascript pour bazel de 5 entreprises différentes sur GitHub, ainsi que celle officielle de Google. Ça vaut la peine d’y penser.
9) Approches générales
En réponse à l'article original, le CTO de Chef a écrit sa réponse
Monorepo: s'il vous plaît! . Dans sa réponse, il soutient que "l'essentiel du monorepo est qu'il vous fait parler et rend les défauts visibles". Il signifie que lorsque vous souhaitez modifier votre API, vous devrez trouver toutes ses utilisations et discuter de vos modifications avec les responsables de ces morceaux de code.
Mon expérience est donc exactement le contraire. Il est clair que cela dépend beaucoup de la culture d'ingénierie de l'équipe, mais je vois de solides inconvénients dans cette approche. Imaginez que vous utilisez une certaine approche qui vous a fidèlement servi pendant un certain temps. Et vous avez donc décidé pour une raison quelconque, en résolvant un problème similaire, d'utiliser une méthode légèrement différente, peut-être plus moderne. Quelle est la probabilité que l'ajout d'une nouvelle approche passe par un examen?
Dans mon passé récent, j'ai reçu plusieurs fois des commentaires tels que «nous avons déjà un chemin prouvé, utilisez-le» et «si vous voulez mettre en œuvre une nouvelle approche, mettez à jour le code dans les 120 endroits où l'ancienne approche est utilisée et obtenez la mise à jour de toutes les équipes responsables de ces morceaux de code. " Habituellement, l'enthousiasme de l '«innovateur» s'arrête là.
Et combien, à votre avis, cela coûtera-t-il d'écrire un nouveau service dans un nouveau langage de programmation? Dans le référentiel - pas du tout. Vous créez un nouveau référentiel et écrivez, et prenez même le système de construction le plus approprié. Et maintenant la même chose dans le monorepositaire?
Je comprends très bien que «standardisation, réutilisation, partage de code», mais le projet doit être développé. À mon avis subjectif, un monorepositaire empêche plutôt cela.
10) Open source
Récemment, on m'a demandé: «
existe-t-il des outils open source pour les mono-référentiels? » J'ai répondu: «Le problème est que les outils pour les mono-référentiels, assez curieusement, sont développés à l'intérieur du mono-référentiel lui-même. Par conséquent, les mettre en open source est assez difficile! »
Par exemple, regardez un projet sur Github avec un
plugin bazel pour Intellij IDEA . Google le développe dans son référentiel interne, puis en «éclabousse» certaines parties sur Github avec une perte d'historique de validation, sans possibilité d'envoyer une demande de pull, etc. Je ne pense pas que ce soit open source (voici un exemple de
mon petit PR , qui a été fermé, au lieu d'une fusion, puis les changements sont apparus dans la prochaine version). Soit dit en passant, ce fait a été mentionné dans l'article d'origine que les mono-référentiels les empêchent de publier en open-source et de créer une communauté autour du projet. Je pense que beaucoup n'ont pas attaché beaucoup d'importance à cet argument.
Alternatives
Eh bien, si nous parlons de quoi faire pour éviter tous ces problèmes? Il y a exactement un conseil - efforcez-vous d'avoir un référentiel aussi petit que possible.
Mais qu'en est-il du monorepositaire? Et même si cette approche vous prive de la possibilité d'avoir des référentiels petits, légers et indépendants.
Quels sont les inconvénients de l'approche polyréférentielle? Je vois exactement 1: l'incapacité de garder une trace de qui est le consommateur de votre API. Cela est particulièrement vrai de l'approche des microservices
«ne rien partager» , dans laquelle le code ne cherche pas entre les microservices. (Soit dit en passant, pensez-vous que quelqu'un utilise cette approche dans les mono-référentiels?) Malheureusement, ce problème doit être résolu soit par des moyens organisationnels, soit essayer d'utiliser des outils de navigation dans le code qui prennent en charge des référentiels indépendants (par exemple,
https://sourcegraph.com / ).
Qu'en est-il des commentaires comme
«nous avons essayé des polyrepositories, mais nous avons ensuite dû constamment implémenter des fonctionnalités dans plusieurs référentiels à la fois, ce qui était fastidieux, et nous avons tout fusionné dans une seule chaudière» ? La réponse à cette question est très simple:
"ne confondez pas les problèmes de l'approche avec une décomposition incorrecte .
" Personne ne prétend que le référentiel doit contenir exactement un microservice et c'est tout. Lorsque j'utilisais des polyrepositoires, nous avons parfaitement réuni une famille de microservices étroitement liés dans un même référentiel. Néanmoins, compte tenu du fait qu'il y avait plus de 100 services, il y avait plus de 20 référentiels de ce type. La chose la plus importante à considérer en termes de décomposition est de savoir comment ces services seront déployés.
Mais qu'en est-il de l'argument de la version? Après tout, les mono-référentiels vous permettent de n'avoir aucune version et de tout déployer à partir d'un seul commit! Premièrement, le versioning est le plus simple de tous les problèmes évoqués ici. Même dans une vieille chose comme maven, il existe un plugin maven-version qui vous permet de rétrograder la version en un seul clic. Et deuxièmement et surtout, votre entreprise dispose-t-elle d'applications mobiles? Si c'est le cas, alors vous avez déjà des versions, et vous n'irez nulle part!
Eh bien, il y a toujours l'argument principal en faveur des mono-référentiels - cela vous permet de refactoriser la base de code en un seul commit! En fait, non. Comme mentionné dans l'article d'origine, en raison des limitations imposées par le déploiement. Vous devez toujours garder à l'esprit que pendant longtemps (la durée dépend de la façon dont votre processus est construit), vous aurez 2 versions du même service en parallèle. Par exemple, sur mon dernier projet, notre système était dans cet état pendant plusieurs heures à chaque déploiement. Cela conduit au fait qu'il est impossible de procéder à des refactorings globaux affectant les interfaces d'interaction dans un seul commit, même dans un mono-référentiel.
Au lieu d'une conclusion:
Donc, ces respectés et quelques collègues qui travaillent dans Google, Facebook, etc. et venez ici pour défendre leurs mono-référentiels, je veux dire: "Ne vous inquiétez pas, vous faites tout correctement, profitez de votre réglage, qui a été dépensé des centaines de milliers ou des millions d'heures humaines. Ils ont déjà été dépensés, donc si vous ne les utilisez pas, personne ne le fera. "
Et à tout le monde:
"Vous n'êtes pas Google, n'utilisez pas de mono-référentiels!"P.S. comme l'a noté le respecté Bobuk dans le podcast
radio-T lors de la discussion de l'article original: «Il y a environ 20 entreprises dans le monde qui peuvent utiliser un seul référentiel.
Les autres ne devraient même pas essayer . »