Ci sono due strategie principali per ospitare e gestire il codice attraverso Git: monorepo vs multi-repo. Entrambi gli approcci hanno i loro pro e contro e possiamo usarli per qualsiasi codebase in qualsiasi lingua. È possibile usare una qualsiasi di queste strategie per progetti che contengono da una manciata a migliaia di librerie.
La scelta tra monorepo o multi-repo varia in base a molti fattori, e potete optare per l’una o l’altra sia che abbiate pochi membri nel team o centinaia, che vogliate ospitare codice privato o open-source.

Quali sono i vantaggi e gli svantaggi di ogni approccio? Quando dovremmo usare l’uno o l’altro? Scopriamolo!

Cosa Sono i Repo?

Un repo (abbreviazione di repository) è un archivio per tutti i cambiamenti e i file di un progetto che permette a sviluppatrici e sviluppatori di “controllare le versioni” delle risorse del progetto durante tutta la sua fase di sviluppo.

Di solito ci riferiamo ai repository Git (come quelli forniti da GitHub, GitLab, o Bitbucket), ma il concetto si applica anche ad altri sistemi di controllo di versione (come Mercurial).

Cos’È un Monorepo?

L’approccio monorepo usa un singolo repository per ospitare tutto il codice per le molteplici librerie o servizi che compongono i progetti di un’azienda. Al suo estremo, l’intero codebase di un’azienda – che abbraccia vari progetti e codifica in diversi linguaggi – è ospitata in un unico repository.

Vantaggi di Monorepo

Ospitare l’intero codebase su un singolo repository offre diversi vantaggi. Li vediamo di seguito:

Abbassa le Barriere d’Ingresso

Quando nuove persone iniziano a lavorare per un’azienda, hanno bisogno di scaricare il codice e installare gli strumenti necessari per iniziare a occuparsi dei loro compiti. Supponiamo che il progetto sia suddiviso su molti repository, ognuno dei quali ha le sue istruzioni di installazione e gli strumenti richiesti. In questo caso, la configurazione iniziale sarà complessa e il più delle volte la documentazione non sarà completa, cosa che richiede alle nuove persone del team di rivolgersi ai colleghi per un aiuto.

Un monorepo semplifica le cose. Dal momento che c’è un unico luogo che contiene tutto il codice e la documentazione, è possibile semplificare la configurazione iniziale.

Gestione del Codice a Livello Centrale

Avere un unico repository permette a tutte le persone del team sviluppo di avere visibilità su tutto il codice. Semplifica la gestione del codice perché possiamo usare un unico issue tracker per controllare tutti i problemi durante il ciclo di vita dell’applicazione.

Queste caratteristiche sono per esempio preziose quando un problema si estende su due (o più) librerie child con un bug esistente sulla libreria dipendente. Con più repository, può essere difficile trovare il pezzo di codice in cui si verifica il problema.

Oltre a questo, avremmo bisogno di capire quale repository usare per creare la issue e poi invitare con cross-tag i membri di altri team per aiutare a risolvere il problema.

Con un monorepo, però, sia la localizzazione dei problemi del codice che la collaborazione per la risoluzione dei problemi diventano più semplici da realizzare.

Refactoring Indolore a Livello di Applicazione

Quando si crea un refactoring del codice a livello di applicazione, saranno interessate più librerie. Se le state ospitando tramite repository multipli, gestire tutte le diverse richieste di pull per mantenerle sincronizzate tra loro può rivelarsi una sfida.

Un monorepo facilita l’esecuzione di tutte le modifiche su tutto il codice per tutte le librerie per presentarle sotto un’unica pull request.

È Più Difficile Rompere Funzionalità Adiacenti

Con il monorepo possiamo impostare tutti i test per tutte le librerie da eseguire ogni volta che una singola libreria viene modificata. Di conseguenza, la probabilità che le modifiche in alcune librerie abbiano effetti negativi sulle altre si riduce.

I Team Condividono la Cultura dello Sviluppo

Anche se non impossibile, con un approccio monorepo diventa difficile ispirare sottoculture uniche tra diversi team. Dal momento che condivideranno lo stesso repository, molto probabilmente condivideranno le stesse metodologie di programmazione e gestione e useranno gli stessi strumenti di sviluppo.

Problemi con l’Approccio Monorepo

Usare un unico repository per tutto il nostro codice ha diversi svantaggi.

Cicli di Sviluppo più Lenti

Quando il codice di una libreria contiene modifiche che fanno fallire i test per le librerie dipendenti, anche il codice va corretto prima di unire le modifiche.

Se queste librerie dipendono da altri team, che sono occupati a lavorare su qualche altro compito e non sono in grado (o disposti) ad adattare il loro codice per superare i test, lo sviluppo della nuova funzionalità può bloccarsi.

Inoltre, il progetto potrebbe iniziare ad avanzare alla velocità del team più lento dell’azienda. Questo risultato potrebbe essere frustrante per i membri dei team più veloci, creando condizioni che potrebbero invogliare a lasciare l’azienda.

Inoltre, una libreria dovrà eseguire i test anche per tutte le altre librerie. Più test ci sono, più tempo ci vuole per eseguirli, e questo rallenta la velocità con cui possiamo iterare il nostro codice.

Richiede il Download dell’Intero Codebase

Quando il monorepo contiene tutto il codice di un’azienda, può diventare enorme e contenere diversi gigabyte di dati. Per contribuire a qualsiasi libreria ospitata al suo interno, ci sarebbe bisogno di scaricare l’intero repository.

Avere a che fare con un codebase ingente implica un cattivo uso dello spazio sui nostri dischi rigidi e interazioni più lente con esso. Per esempio, le azioni quotidiane come l’esecuzione di git status o la ricerca nel codebase con una regex possono richiedere molti secondi o addirittura minuti in più rispetto a quanto accadrebbe con più repo.

Le Librerie Non Modificate Possono Essere di Nuova Versione

Quando tagghiamo il monorepo, tutto il codice al suo interno adotta il nuovo tag. Se questa azione innesca un nuovo rilascio, allora tutte le librerie ospitate nel repository saranno nuovamente rilasciate con il numero di versione del tag, anche se molte di queste librerie potrebbero non aver subito alcun cambiamento.

Il Forking È Più Difficile

I progetti open source devono facilitare il più possibile il coinvolgimento dei collaboratori. Con repository multipli, i collaboratori possono andare direttamente al repository specifico per il progetto a cui vogliono contribuire. Nel caso di un monorepo che ospita vari progetti, però, i collaboratori devono prima orientarsi nel progetto giusto e avranno bisogno di capire come il loro contributo può influenzare tutti gli altri progetti.

Cos’È il Multi-Repo?

L’approccio multi-repo utilizza diversi repository per ospitare le molteplici librerie o servizi di un progetto sviluppato da un’azienda. Al suo estremo, ospiterà ogni minimo set di codice riutilizzabile o funzionalità autonoma (come un microservizio) sotto il suo repository.

Vantaggi del Multi-Repo

Ospitare ogni libreria indipendentemente da tutte le altre presenta moltissimi vantaggi.

Versioning Indipendente della Libreria

Quando si tagga un repository, a tutto il suo codebase viene assegnato il tag “new”. Dal momento che solo il codice di una specifica libreria è sul repository, la libreria può essere taggata e messa in versione indipendentemente da tutte le altre librerie ospitate altrove.

Avere una versione indipendente per ogni libreria aiuta a definire l’albero delle dipendenze per l’applicazione, e ci permette di configurare la versione di ogni libreria da usare.

Rilascio di Servizio Indipendente

Dal momento che il repository contiene solo il codice per qualche servizio e nient’altro, può avere un proprio ciclo di deployment, indipendentemente da qualsiasi progresso delle applicazioni che vi accedono.

Il servizio può usare un ciclo di rilascio veloce come la distribuzione continua (dove il nuovo codice viene distribuito dopo che ha superato tutti i test). Alcune librerie che accedono al servizio possono usare un ciclo di rilascio più lento, come quelle che producono un nuovo rilascio solo una volta alla settimana.

Aiuta a Definire il Controllo degli Accessi in Tutta l’Organizzazione

Solo i membri del team coinvolti nello sviluppo di una libreria devono essere aggiunti al repository corrispondente e scaricare il suo codice. Di conseguenza, c’è una strategia implicita di controllo degli accessi per ogni livello dell’applicazione. Le persone coinvolte con la libreria avranno i permessi di modifica, mentre tutte le altre potrebbero non avere accesso al repository. Oppure possono ricevere permessi di lettura ma senza modifica.

Permette ai Team di Lavorare in Modo Autonomo

I membri del team possono progettare l’architettura della libreria e implementare il suo codice lavorando in isolamento da tutti gli altri team. Possono prendere decisioni basate su ciò che la libreria fa nel contesto generale senza essere influenzati dai requisiti specifici di qualche team o applicazione esterna.

Problemi con l’Approccio Multi-Repo

L’utilizzo di più repository può dare origine a diversi problemi.

Le Librerie Devono Essere Costantemente Risincronizzate

Quando viene rilasciata una nuova versione di una libreria che contiene modifiche, le librerie che dipendono da questa libreria dovranno essere adattate per iniziare a usare l’ultima versione. Se il ciclo di rilascio della libreria è più veloce di quello delle sue librerie dipendenti, esse potrebbero perdere rapidamente la sincronizzazione

I team avranno bisogno di mettersi costantemente in pari per usare le ultime release degli altri team. Dato che diversi team hanno diverse priorità, questo obiettivo può a volte essere difficile da raggiungere.

Di conseguenza, un team che non è in grado di recuperare il ritardo può finire con l’attenersi alla versione obsoleta della libreria da cui dipende. Questo risultato avrà un certo impatto sull’applicazione (in termini di sicurezza, velocità e altre considerazioni), e il divario nello sviluppo tra le librerie può solo aumentare.

Può Frammentare i Team

Quando i diversi team non hanno bisogno di interagire tra loro, finiscono a lavorare nei loro silos. A lungo termine, questo potrebbe portare i team a generare delle sottoculture aziendali, come l’impiego di diverse metodologie di programmazione o di gestione, o l’utilizzo di diversi set di strumenti di sviluppo.

Se poi qualche membro del team deve passare a un team diverso, potrebbe scontrarsi con un certo shock culturale e dover imparare un nuovo modo di fare il suo lavoro.

Monorepo vs Multi-Repo: Differenze Principali

Entrambi gli approcci alla fine si occupano dello stesso obiettivo: gestire il codebase. Questo significa che entrambi devono risolvere le stesse sfide, tra cui la gestione dei rilasci, la promozione della collaborazione tra i membri del team, la gestione dei problemi, l’esecuzione dei test e altro.

La loro principale differenza riguarda le tempistiche con cui i membri del team devono prendere le decisioni: in anticipo per i monorepo, a posteriori per i multi-repo.

Analizziamo questa idea in modo più dettagliato.

Poiché tutte le librerie sono versionate indipendentemente nel multi-repo, un team che rilascia una libreria con modifiche che rompono il codice può agire in modo sicuro assegnando un nuovo numero di versione principale all’ultima release. Altri gruppi possono far aderire le loro librerie dipendenti alla vecchia versione e passare a quella nuova una volta che il loro codice è stato adattato.

Questo approccio lascia la decisione di quando adattare tutte le altre librerie a ciascun team responsabile, che può farlo in qualsiasi momento. Se lo fanno troppo tardi e vengono rilasciate nuove versioni delle librerie, colmare il divario tra le librerie diventerà sempre più difficile.

Di conseguenza, mentre un team può iterare velocemente e con frequenza il proprio codice, gli altri team possono dimostrarsi incapaci di mettersi al passo, producendo alla fine librerie che divergono.

D’altra parte, in un ambiente monorepo, non possiamo rilasciare una nuova versione di una libreria che ne rompe altre perché i loro test fallirebbero. In questo caso, il primo team deve comunicare con il secondo team per incorporare le modifiche.

Questo approccio obbliga i team ad adattare tutte le librerie allo stesso tempo ogni volta che viene implementato un cambiamento per una singola libreria. Tutti i team sono costretti a parlare tra loro e a raggiungere una soluzione insieme.

Di conseguenza, il primo team non sarà in grado di iterare velocemente come vorrebbe, ma perlomeno il codice tra le diverse librerie non divergerà.

In sintesi, l’approccio multi-repo può aiutare a creare una cultura del “muoviti in fretta e rompi tutto” tra i team, dove i team indipendenti e agili possono produrre il loro output alla velocità che vogliono. Invece, l’approccio monorepo favorisce una cultura di consapevolezza e cura, dove i team non dovrebbero essere lasciati indietro ad affrontare un problema da soli.

Approccio Ibrido Poly-As-Mono

Se non possiamo decidere se usare l’approccio multi-repo o monorepo, c’è anche l’approccio intermedio: usare più repository e impiegare qualche strumento per tenerli sincronizzati, rendendolo simile a un monorepo ma con più flessibilità.

Meta è uno di questi strumenti. Organizza più repository in sottodirectory e fornisce un’interfaccia a riga di comando che esegue lo stesso comando su tutti loro contemporaneamente.

Un meta-repository contiene le informazioni su quali repository compongono un progetto. Clonando questo repository tramite meta si cloneranno ricorsivamente tutti i repository richiesti, e questo permette ai nuovi membri del team di iniziare a lavorare immediatamente sui loro progetti.

Per clonare un meta-repository e tutti i suoi repo multipli definiti, dobbiamo eseguire quanto segue:

meta git clone [meta repo url]

Meta eseguirà un git clone per ogni repository e lo metterà in una sottocartella:

Clonare un metaprogetto.
Clonare un metaprogetto. (Fonte immagine: github.com/mateodelnorte/meta)

Da quel momento in poi, l’esecuzione del comando meta execeseguirà il comando su ogni sottocartella. Per esempio, per eseguire git checkout master su ogni repository si fa così:

meta exec "git checkout master"

Approccio Ibrido Mono-As-Poly

Un altro approccio è gestire il codice tramite un monorepo per lo sviluppo, ma copiare il codice di ogni libreria nel suo repository indipendente per il deployment.

Questa strategia è molto comune nell’ecosistema PHP perché Packagist (il repository principale di Composer) richiede un URL pubblico del repository per pubblicare un pacchetto, e non è possibile indicare che il pacchetto si trova in una sottodirectory del repository.

Data la limitazione di Packagist, i progetti PHP possono ancora usare un monorepo per lo sviluppo, ma devono usare l’approccio multi-repo per il deployment.

Per ottenere questa conversione, possiamo eseguire uno script con git subtree split o usare uno degli strumenti disponibili che eseguono la stessa logica:

Chi Sceglie Monorepo vs Multi-Repo

Diverse grandi aziende tecnologiche favoriscono l’approccio monorepo, mentre altre hanno deciso di utilizzare il metodo multi-repo.

Google, Facebook, Twitter e Uber hanno tutti garantito pubblicamente di adottare l’approccio monorepo. Microsoft gestisce il più grande monorepo Git del pianeta per ospitare il codice sorgente del sistema operativo Windows.

Sul lato opposto, Netflix, Amazon e Lyft sono alcune delle aziende famose che usano l’approccio multi-repo.

Sul lato ibrido poly-as-mono, Android aggiorna più repository, che sono gestiti come un monorepo.

Sul lato ibrido mono-as-poly, Symfony mantiene il codice per tutti i suoi componenti in un monorepo. Lo dividono in repository indipendenti per il deployment (come symfony/dependency-injection e symfony/event-dispatcher.)

Esempi di Monorepo e Multi-Repo

L’account WordPress su GitHub ospita esempi di entrambi gli approcci monorepo e multi-repo.

Gutenberg, l’editor a blocchi di WordPress, è composto da diverse decine di pacchetti JavaScript. Questi pacchetti sono tutti ospitati sul monorepo WordPress/gutenberg e gestiti attraverso Lerna per aiutare a pubblicarli nel repository npm.

Openverse, il motore di ricerca per media con licenza aperta, ospita le sue parti principali in repository indipendenti: Front-end, Catalog e API.

Monorepo vs Multi-Repo: Come Scegliere?

Come per molti problemi di sviluppo, non c’è una risposta predefinita su quale approccio è meglio usare. Aziende e progetti diversi beneficeranno di una strategia o dell’altra in base al loro contesto unico, come:

  • Quanto è grande il codebase? Contiene gigabyte di dati?
  • Quante persone lavoreranno sul codebase? 10, 100, 1000?
  • Quanti pacchetti ci saranno? 10, 100, 1000?
  • Su quanti pacchetti deve lavorare il team in un dato momento?
  • Quanto sono strettamente accoppiati tra loro i pacchetti?
  • Sono coinvolti diversi linguaggi di programmazione? Richiedono l’installazione di un software particolare o un hardware speciale per funzionare?
  • Quanti strumenti di distribuzione sono necessari e quanto sono complessi da impostare?
  • Qual è la cultura dell’azienda? I team sono incoraggiati a collaborare?
  • Quali strumenti e tecnologie sanno usare i team?

Riepilogo

Ci sono due strategie principali per ospitare e gestire il codice: monorepo vs multi-repo. L’approccio monorepo comporta la memorizzazione del codice per diverse librerie o progetti – o anche tutto il codice di una società – in un unico repository. Il sistema multi-repo, invece, divide il codice in unità, come librerie o servizi, e mantiene il loro codice ospitato in repository indipendenti.

Quale approccio scegliere dipende da molteplici condizioni. Entrambe le strategie presentano diversi vantaggi e svantaggi, e li abbiamo coperti tutti in dettaglio in questo articolo.

Avete domande su monorepo o multi-repo? Scrivetecele nella sezione commenti!

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.