Er zijn twee hoofdstrategieën voor het hosten en beheren van code via Git: mono-repo en multi-repo. Ze hebben allebei voordelen en nadelen.

Beide strategieën zijn te gebruiken voor codebases in alle programmeertalen. Je kan beide strategieën toepassen voor projecten die maar een paar libraries gebruiken of juist duizenden. Zelfs of het nou gaat om een paar teamleden of complete afdelingen, en of je privé host of een open source aanpak kiest, dan kan je nog steeds kiezen tussen multi-repo en mono-repo.

Dus wat zijn precies de voordelen en nadelen van beide aanpakken? Wanneer moeten we de ene of juist de andere gebruiken? Laten we daar snel achter komen!

Wat zijn repos?

Een repo (de afkorting van repository, oftewel een verzamelplek) is een opslagplek voor alle veranderingen, versies en bestanden van een project, waardoor developers “versiebeheer” kunnen uitvoeren op alle onderdelen van een project tijdens de ontwikkeling.

We refereren meestal aan Git repositories (zoals aangeboden door GitHubGitLab, of Bitbucket), maar het concept is ook toe te passen op andere versiebeheersystemen (bijvoorbeeld Mercurial).

Wat is een mono-repo?

De route van een mono-repo gebruikt een enkele repository voor alle code van meerdere libraries of diensten waaruit een project of meerdere projecten bestaan. Als je het volledig doorvoert kan de hele codebase van een bedrijf, met verschillende projecten en programmeertalen, zelfs gehost worden in één enkele repository.

Voordelen van een mono-repo

Het hosten van een gehele codebase in één repository heeft de onderstaande voordelen.

Lagere drempels om te beginnen

Wanneer nieuwe medewerkers beginnen te werken voor een bedrijf, moeten ze de code downloaden en de vereiste tools installeren om aan de slag te kunnen. Stel dat het project verdeeld is over verschillende repositories, allemaal met eigen instructies voor het installeren en vereiste tools. In dat geval zal de eerste installatie nogal complex worden, en meestal zal de documentatie ook niet helemaal compleet zijn, waardoor de nieuwe teamleden hulp moeten vragen aan collega’s.

Een mono-repo vereenvoudigt dit allemaal. Aangezien er maar één locatie is waar alle code en documentatie staat, kan je de start van nieuwe leden eenvoudiger maken.

Centraal codebeheer

Door maar één repository te gebruiken zien alle developers in principe alle code. Dit vereenvoudigt het beheer van code aangezien we een enkele issue tracker kunnen gebruiken voor het bijhouden van alle problemen in de hele levenscyclus van een toepassing.

Deze voordelen zijn waardevol wanneer een probleem te maken heeft met meerdere onderliggende libraries, waarbij de bug in de afhankelijke library zit. Met meerdere repositories kan het nogal uitdagend worden om het precieze stukje code te vinden waar het probleem vandaan komt.

Daarnaast zouden we moeten uitvogelen welke repository we moeten gebruiken om het probleem op te gaan lossen, en vervolgens alle andere teamleden daarvoor uit te nodigen.

Maar met een mono-repo is het lokaliseren van problemen in de code en het samenwerken aan een oplossing veel eenvoudiger.

Eenvoudig refactoren van complete toepassingen

Wanneer je de complete code van een hele toepassing gaat refactoren, zullen daar meerdere libraries bij betrokken worden. Als je die op verschillende repositories hebt staan, zal het beheren van alle verschillende pull-request en ze allemaal gesynchroniseerd houden een flinke uitdaging worden.

Een mono-repo maakt het makkelijker om alle wijzigingen in alle code te synchroniseren en alles in één pull request te versturen.

Gerelateerde functies gaan minder snel kapot

Met een mono-repo kunnen we de tests voor alle libraries uitvoeren, elke keer wanneer er maar één library aangepast wordt. Daardoor is de kans veel kleiner dat je een verandering maakt waarvan later blijkt dat het een probleem oplevert in andere libraries.

Teams delen dezelfde developmentcultuur

Met een mono-repo aanpak is het bijna onmogelijk dat er verschillende subculturen ontstaan onder verschillende teams. Aangezien ze allemaal dezelfde repository gebruiken, zullen ze ook meestal dezelfde programmeermethoden en beheerstrategieën gebruiken en ook dezelfde developmenttools.

Nadelen van een aanpak met een mono-repo

Het gebruiken van één repository voor alle code heeft natuurlijk ook nadelen.

Tragere developmentcyclus

Wanneer de code voor een library blokkerende veranderingen bevat, waardoor ook de tests voor gerelateerde libraries vastlopen, zal de code eerst overal gecorrigeerd moeten worden voordat wijzigingen doorgevoerd kunnen worden.

Als deze libraries door andere teams gemaakt worden, die met eigen prioriteiten bezig zijn en daardoor moeite hebben om hun code aan te passen om jouw problematische wijzigingen te accommoderen zodat alle tests lukken, kan de ontwikkeling van een nieuwe feature vertraging oplopen.

Daarmee loop je zo het risico dat het hele project zo snel gaat als het traagste team. Dit kan erg frustrerend zijn voor snellere teams, wat nogal demotiverend is.

Daarnaast moet een library ook de tests voor alle andere libraries uitvoeren. Hoe meer tests je moet uitvoeren, hoe langer ze duren, en hoe minder snel je een cyclus kan afronden.

Vereist downloaden van gehele codebase

Doordat de mono-repo alle code van de organisatie bevat, kan dit een enorme structuur opleveren, van vele gigabytes. Wanneer iemand aan een onderliggende library wil werken, zullen ze de hele repository moeten downloaden.

Het werken met een grote codebase levert een inefficiënt gebruik van opslagruimte op, en betekent ook dat je er minder snel mee kan werken. Zo worden alledaagse acties zoals bijvoorbeeld het uitvoeren van git status of zoeken in de codebase met een regex erg traag, waardoor ze wel minuten langer kunnen duren dan wanneer je een opgesplitste codebase had.

Er worden nieuwe versies aangemaakt van onveranderde libraries

Wanneer we de mono-repo taggen, krijgt alle onderliggende code een nieuwe tag toegewezen. Als deze actie een nieuwe release veroorzaakt, zullen alle libraries die in de repository gehost worden gereleased worden met het versienummer van de tag, ook al zullen veel van de libraries helemaal geen wijzigingen hebben gehad.

Forken wordt moeilijker

Open source projecten moeten het zo makkelijk mogelijk maken voor bijdragers om met het project mee te doen. Met meerdere repositories kunnen ze meteen naar het specifieke onderdeel van een project gaan waar ze aan willen bijdragen. Maar wanneer een mono-repo meerdere projecten bevat, zullen bijdragers eerst de hele structuur door moeten, en moeten ze begrijpen hoe hun bijdrage invloed heeft op alle andere projecten.

Wat is een multi-repo?

De multi-repo route gebruikt juist meerdere repositories om meerdere libraries of services van een project te hosten. In het meest extreme geval zal elke minimale set van herbruikbare code of onafhankelijke functie (zoals een microservice) in een eigen repository gehost worden.

Voordelen van een multi-repo

Het hosten van elke library onafhankelijk van de anderen biedt allerlei voordelen.

Onafhankelijk versies maken van libraries

Wanneer je een repository tagt, zal de hele codebase de “nieuwe” tag toegewezen krijgen. Aangezien alleen de code voor een specifieke library op de repository zit, zal alleen die library getagd worden en een nieuwe versie krijgen, onafhankelijk van andere libraries.

Door een onafhankelijk versie te hebben voor elke library kan je de dependency tree voor de toepassing opbouwen, waardoor je kan configureren welke versie van elke library er gebruikt moet worden.

Onafhankelijke releases van services

Aangezien de repository alleen de code voor een bepaalde service bevat en verder niks, kan een onafhankelijke deploymentcyclus gebruikt worden, onafhankelijk van de voortgang op andere onderdelen die er gebruik van maken.

De service kan daardoor een snelle releasecyclus gebruiken, idealiter zelfs continuous delivery (waarbij nieuwe code meteen wordt geïmplementeerd zodra alle tests gelukt zijn). Sommige libraries die gebruik maken van de service hebben wellicht een tragere releasecyclus, bijvoorbeeld met slechts één nieuwe release per week.

Maakt toegangscontrole binnen de organisatie eenvoudiger

Alleen teamleden die werken aan de ontwikkeling van een bepaalde library hoeven toegang te hebben tot de bijbehorende library en de code te downloaden. Daardoor zit er meteen een toegangscontrole op elke laag van de toepassing. Iedereen die werkt met de library kan het recht om te wijzigen krijgen, en verder krijgt niemand toegang. Eventueel zou je ook alleen-lezen rechten kunnen geven aan sommige mensen.

Maakt het mogelijk dat teams onafhankelijk werken

Teamleden kunnen de architectuur van een library ontwerpen en de code implementeren, zonder rekening te hoeven houden met andere teams. Ze kunnen beslissingen maken op basis van wat de library doet in het algemeen, zonder na te hoeven denken over de specifieke vereisten van een bepaald ander team of andere toepassing.

Nadelen van een aanpak met een multi-repo

Het gebruiken van meerdere repositories kan natuurlijk ook nadelen opleveren.

Libraries moeten steeds opnieuw gesynchroniseerd worden

Wanneer een nieuwe versie van een library blokkerende wijzigingen bevat, moeten alle afhankelijke libraries aangepast worden aan de nieuwste versie. Als de releasecyclus van de library korter en sneller is dan die van afhankelijke libraries, kan de synchronisatie al snel mis gaan lopen.

Teams zijn dan vooral bezig met het inhalen van releases van andere teams, in plaats van met hun eigen werk. Aangezien verschillende teams verschillende prioriteiten kunnen hebben, kan dit een uitdagende situatie opleveren.

En wanneer een team het niet bij kan of wil benen, kan het zijn dat ze uiteindelijk een verouderde versie van een library gaan gebruiken. Dit heeft natuurlijk allerlei gevolgen voor het eindresultaat (qua beveiliging, snelheid en andere factoren), waardoor het gat in de ontwikkeling tussen verschillende libraries nog groter wordt.

Teams kunnen gefragmenteerd raken

Wanneer verschillende teams niet met andere teams hoeven samen te werken, kunnen ze zich terug gaan trekken in hun eigen silo. Op lange term kan dat ervoor zorgen dat je aparte subculturen binnen de organisatie krijgt, zoals het gebruik van verschillende programmeermethoden of zelfs verschillende developmenttools.

Als een teamlid dan naar een ander team moet overstappen of ermee moet samenwerken, kan dat een cultuurschok opleveren en moeten ze opnieuw leren hoe ze hun werk moeten doen.

Mono-repo of multi-repo: belangrijkste verschillen

Beide aanpakken hebben uiteindelijk hetzelfde doel: de codebase beheren. Daarom moeten beide opties dezelfde uitdagingen oplossen, zoals het beheer van releases, het aanmoedigen van samenwerkingen tussen teams en teamleden, het omgaan met probleem, het uitvoeren van tests en nog veel meer.

Het grootste verschil zit in de kern in de timing van wanneer teamleden beslissingen moeten maken: van tevoren voor een mono-repo of pas later bij een multi-repo.

Laten we dat verder analyseren.

Aangezien alle libraries een eigen versie krijgen in een multi-repo, kan een team dat een library met blokkerende wijzigingen releaset dit veilig doen door een nieuw grote releasenummer toe te wijzen aan deze release. Andere teams kunnen er dan voor kiezen om bij de oude versie te blijven en over te stappen wanneer hun code is aangepast.

Met deze aanpak kan elk verantwoordelijk team zelf kiezen wanneer ze hun libraries willen aanpassen op anderen. Als ze dit te laat doen en er al weer een nieuwe versies is, komt er natuurlijk een steeds groter gat.

Daardoor kan het ene team heel snel wijzigingen aan het doorvoeren zijn aan hun code, terwijl andere teams het niet kunnen bijbenen, waardoor ze libraries maken die uit elkaar lopen.

Aan de andere kant kan je in een mono-repo geen nieuwe versie van een library releasen wanneer die een andere library verstoort, aangezien alle tests dan misgaan. In dat geval moet het eerste team eerst overleggen met het afhankelijk team om de wijzigingen te kunnen verwerken.

Deze aanpak verplicht teams om al hun libraries op elkaar aan te passen, elke keer dat er een library gewijzigd wordt. Alle teams moeten daardoor steeds met elkaar communiceren en een gezamenlijke oplossing vinden.

Daardoor zal het snelle team minder snel wijzigingen door kunnen voeren, maar de code zal ook niet uit elkaar gaan lopen tussen libraries.

Samenvattend kan de route van de multi-repo dus een cultuur aanmoedigen waarbij iedereen snel werkt, ook al gaat er dan soms iets mis, waardoor de snellere teams ook echt hun maximale output kunnen leveren. Kies je voor de aanpak van de mono-repo, dan krijg je een voorzichtigere cultuur waarbij iedereen met elkaar praat, en er geen team achteropraakt.

Hybride poly-als-mono aanpak

Als je niet kan kiezen tussen de multi-repo of mono-repo aanpak, is er ook nog een middenweg: gebruik meerdere repositories en zet een tool in om ze gesynchroniseerd te houden, waardoor het lijkt op een mono-repo, maar met meer flexibiliteit.

Meta is zo’n tool. De tool organiseert meerdere repositories in submappen en biedt een command-line interface waarmee dezelfde opdracht tegelijkertijd op alle repositories uitgevoerd wordt.

Een meta-repository bevat alle informatie over de repositories waar een project uit bestaat. Het klonen van deze repository via meta zal herhalend alle vereiste repositories klonen, waardoor nieuwe teamleden ook sneller aan de gang kunnen met hun projecten.

Om een meta-repository te klonen inclusief alle gedefinieerde repos eronder, voeren we de volgende opdracht uit:

meta git clone [meta repo url]

Meta zal een git clone uitvoeren voor elke repository en deze in een submap plaatsen:

Klonen van een meta-project.
Klonen van een meta-project. (Afbeelding: github.com/mateodelnorte/meta)

Vervolgens kan je de meta exec opdracht uitvoeren om de opdracht uit te voeren op elke submap. Zo kan je bijvoorbeeld git checkout master uitvoeren op elke repository:

meta exec "git checkout master"

Hybride mono-als-poly aanpak

Een andere mogelijkheid is het beheren van de code via een mono-repo tijdens de development, maar tijdens deployment de code van elke library kopiëren in een onafhankelijke repository.

Deze strategie wordt veel gebruikt binnen het PHP ecosysteem omdat Packagist (het belangrijkste Composer repository) een publieke URL voor de repository vereist om een package te kunnen publiceren. Het is daarbij niet mogelijk om aan te geven dat die package te vinden is in een submap van de repository.

Gezien de beperking van Packagist, kunnen PHP projecten nog steeds een mono-repo gebruiken tijdens development, maar moeten ze altijd een multi-repo gebruiken tijdens de deployment.

Om deze omzetting te realiseren, kunnen we een script uitvoeren met git subtree split. Of gebruik één van de beschikbare tools die dezelfde logica toepassen:

Wie gebruikt er mono-repo en multi-repo?

Er zijn diverse grote techbedrijven die voor de mono-repo aanpak kiezen, en andere die juist voor de multi-repo methode gaan.

GoogleFacebookTwitter, en Uber hebben allemaal publiekelijk gekozen voor de mono-repo. Microsoft beheert de grootste Git mono-repo ter wereld voor het hosten van de broncode van het Windows besturingssysteem.

Aan de andere kant zijn NetflixAmazon, en Lyft bekende bedrijven die juist voor de multi-repo gekozen hebben.

Er zijn ook hybride poly-als-mono bedrijven, zoals Android die meerdere repositories updatet, die worden beheerd als mono-repo.

En op de tegengestelde kant van mono-als-poly, beheert Symfony de code voor al hun componenten in een mono-repo. Vervolgens splitsen ze deze op in onafhankelijke repositories tijdens deployment (zoals  symfony/dependency-injection en symfony/event-dispatcher.)

Voorbeelden van mono-repo en multi-repo

Het WordPress account op GitHub host voorbeelden van zowel de mono-repo als de multi-repo.

Gutenberg, de WordPress block editor, is samengesteld uit enkele tientallen JavaScript packages. Deze packages worden allemaal gehost op de WordPress/gutenberg mono-repo en beheerd via Lerna om ze te publiceren in de npm repository.

Openverse, de zoekmachine van openlijk gelicenseerde media, host de belangrijkste onderdelen in onafhankelijke repositories: Front-endCatalog, en API.

Mono-repo of multi-repo: zo maak je een keuze

Zoals met veel uitdagingen in development, is er geen vast antwoord op welke aanpak de beste is. Verschillende organisaties en verschillende projecten zullen voordelen hebben van juist de ene of de andere aanpak op basis van unieke factoren, zoals:

  • Hoe groot is de codebase? Bevat deze meerdere gigabytes aan data?
  • Hoeveel mensen zullen aan de codebase werken? Zit dit rond de 10, 100 of 1000?
  • Hoeveel packages zullen er zijn? Zit dit rond de 10, 100 of 1000?
  • Hoeveel packages moet een team tegelijkertijd aan werken?
  • Hoe nauw werken de packages samen?
  • Worden er verschillende programmeertalen gebruikt? Vereisen ze specifieke software of speciale hardware?
  • Hoeveel deploymenttools zijn er nodig, en hoe complex zijn die?
  • Wat is de cultuur in het bedrijf en in de verschillende teams? Worden teams aangemoedigd om nauw samen te werken?
  • Welke tools en technologieën kunnen de teams gebruiken?

Samenvatting

Er zijn twee hoofdstrategieën voor het hosten en beheren van code: mono-repo en multi-repo. De mono-repo aanpak betekent het opslaan en verzamelen van code voor verschillende libraries of projecten, of zelfs alle code van een hele organisatie, in één repository. En het multi-repo systeem verdeelt de code in eenheden, bijvoorbeeld libraries of onafhankelijke services, en host de benodigde code daarvoor in een onafhankelijke repository.

Welke aanpak je kiest hangt af van allerlei factoren. Beide keuzes hebben voordelen en nadelen, en in dit artikel hebben we daar uitgebreid naar gekeken.

Nog vragen over mono-repos of multi-repos? Laat het ons weten in de reacties hieronder!

Leonardo Losoviz

Leo writes about innovative web development trends, mostly concerning PHP, WordPress and GraphQL. You can find him at leoloso.com and twitter.com/losoviz.