Es gibt zwei Hauptstrategien für das Hosten und Verwalten von Code über Git: Monorepo vs. Multi-Repo. Beide Ansätze haben ihre Vor- und Nachteile.

Wir können beide Ansätze für jede Codebasis in jeder Sprache verwenden. Du kannst jede dieser Strategien für Projekte verwenden, die eine Handvoll bis tausende von Bibliotheken enthalten. Auch wenn es sich um ein paar Teammitglieder oder Hunderte handelt, oder du privaten oder Open Source Code hosten willst, kannst du dich für Monorepo oder Multi-Repo entscheiden, abhängig von verschiedenen Faktoren.

Was sind die Vor- und Nachteile der beiden Ansätze? Wann sollte man das eine oder das andere verwenden? Lass es uns herausfinden!

Was sind Repos?

Ein Repo (kurz für Repository) ist ein Speicher für alle Änderungen und Dateien eines Projekts, der es Entwicklern ermöglicht, die Assets des Projekts während der gesamten Entwicklungsphase zu „versionskontrollieren“.

Wir beziehen uns in der Regel auf Git Repositories (wie sie von GitHub, GitLab oder Bitbucket bereitgestellt werden), aber das Konzept gilt auch für andere Versionskontrollsysteme (wie Mercurial).

Was ist ein Monorepo?

Der Monorepo-Ansatz verwendet ein einziges Repository, um den gesamten Code für die verschiedenen Bibliotheken oder Dienste zu hosten, aus denen die Projekte eines Unternehmens bestehen. Im Extremfall wird die gesamte Codebasis eines Unternehmens – die sich über verschiedene Projekte erstreckt und in unterschiedlichen Sprachen kodiert ist – in einem einzigen Repository gehostet.

Vorteile von Monorepo

Das Hosten der gesamten Codebase in einem einzigen Repository bietet folgende Vorteile.

Senkt die Einstiegshürden

Wenn neue Mitarbeiter in einem Unternehmen anfangen zu arbeiten, müssen sie den Code herunterladen und die benötigten Tools installieren, um mit ihren Aufgaben beginnen zu können. Nehmen wir an, das Projekt ist über viele Repositories verstreut und jedes hat seine eigenen Installationsanweisungen und benötigten Tools. In diesem Fall wird die anfängliche Einrichtung komplex sein und meistens ist die Dokumentation nicht vollständig, sodass die neuen Teammitglieder ihre Kollegen um Hilfe bitten müssen.

Ein Monorepo vereinfacht die Sache. Da es einen einzigen Ort gibt, an dem der gesamte Code und die Dokumentation zu finden sind, kannst du die Ersteinrichtung rationalisieren.

Zentral gelegenes Code Management

Ein einziges Repository zu haben, gibt allen Entwicklern Einblick in den gesamten Code. Es vereinfacht das Code Management, da wir einen einzigen Issue Tracker nutzen können, um alle Issues während des gesamten Lebenszyklus der Anwendung zu beobachten.

Diese Eigenschaften sind zum Beispiel wertvoll, wenn ein Problem zwei (oder mehr) Child-Bibliotheken umspannt und der Fehler in der abhängigen Bibliothek existiert. Mit mehreren Repositories kann es eine Herausforderung sein, die Stelle im Code zu finden, an der das Problem auftritt.

Darüber hinaus müssten wir herausfinden, welches Repository wir für die Erstellung des Problems verwenden und dann Mitglieder anderer Teams einladen und cross-taggen, um bei der Lösung des Problems zu helfen.

Mit einem Monorepo ist es jedoch einfacher, sowohl Codeprobleme zu lokalisieren als auch gemeinsam an der Fehlerbehebung zu arbeiten.

Schmerzfreie anwendungsweite Refactorings

Wenn du ein anwendungsweites Refactoring des Codes durchführst, sind mehrere Bibliotheken betroffen. Wenn du sie über mehrere Repositories hostest, kann es eine Herausforderung sein, all die verschiedenen Pull Requests zu verwalten, um sie miteinander synchron zu halten.

Ein Monorepo macht es einfach, alle Änderungen am Code für alle Bibliotheken durchzuführen und sie unter einem einzigen Pull Request einzureichen.

Schwieriger, benachbarte Funktionalitäten zu brechen

Mit dem Monorepo können wir alle Tests für alle Bibliotheken so einrichten, dass sie immer dann ausgeführt werden, wenn eine einzelne Bibliothek verändert wird. Dadurch ist die Wahrscheinlichkeit, dass eine Änderung in einigen Bibliotheken nachteilige Auswirkungen auf andere Bibliotheken hat, minimiert.

Teams teilen Entwicklungskultur

Auch wenn es nicht unmöglich ist, wird es mit einem Monorepo-Ansatz schwierig, einzigartige Subkulturen zwischen verschiedenen Teams zu inspirieren. Da sie sich das gleiche Repository teilen, werden sie höchstwahrscheinlich auch die gleichen Programmier- und Verwaltungsmethoden verwenden und die gleichen Tools einsetzen.

Probleme mit dem Monorepo-Ansatz

Die Verwendung eines einzigen Repositorys für unseren gesamten Code hat mehrere Nachteile.

Langsamere Entwicklungszyklen

Wenn der Code für eine Bibliothek bahnbrechende Änderungen enthält, die dazu führen, dass die Tests für abhängige Bibliotheken fehlschlagen, muss der Code ebenfalls korrigiert werden, bevor die Änderungen zusammengeführt werden.

Wenn diese Bibliotheken von anderen Teams abhängen, die mit einer anderen Aufgabe beschäftigt sind und nicht in der Lage (oder willens) sind, ihren Code so anzupassen, dass die Änderungen nicht durchschlagen und die Tests bestehen, kann die Entwicklung des neuen Features ins Stocken geraten.

Außerdem kann es sein, dass das Projekt nur mit der Geschwindigkeit des langsamsten Teams im Unternehmen vorankommt. Dieses Ergebnis könnte die Mitglieder der schnellsten Teams frustrieren und die Voraussetzungen dafür schaffen, dass sie das Unternehmen verlassen wollen.

Darüber hinaus muss eine Bibliothek auch die Tests für alle anderen Bibliotheken ausführen. Je mehr Tests ausgeführt werden müssen, desto mehr Zeit braucht es, um sie auszuführen, was die Iterationsgeschwindigkeit unseres Codes verlangsamt.

Erfordert den Download der gesamten Codebase

Wenn die Monorepo den gesamten Code einer Firma enthält, kann sie riesig sein und Gigabytes an Daten enthalten. Um zu einer darin gehosteten Bibliothek beizutragen, würde jeder einen Download des gesamten Repositorys benötigen.

Der Umgang mit einer riesigen Codebasis impliziert eine schlechte Nutzung des Platzes auf unseren Festplatten und langsamere Interaktionen mit ihr. Zum Beispiel können alltägliche Aktionen wie das Ausführen von git status oder die Suche in der Codebase mit einer Regex viele Sekunden oder sogar Minuten länger dauern als bei mehreren Repos.

Unveränderte Bibliotheken können neu versioniert sein

Wenn wir das Monorepo taggen, wird der gesamte Code darin mit dem neuen Tag versehen. Wenn diese Aktion ein neues Release auslöst, dann werden alle Bibliotheken, die im Repository gehostet werden, mit der Versionsnummer aus dem Tag neu veröffentlicht, auch wenn viele dieser Bibliotheken vielleicht gar nicht verändert wurden.

Forking ist schwieriger

Open Source Projekte müssen es den Mitwirkenden so einfach wie möglich machen, sich zu beteiligen. Mit mehreren Repositories können Mitwirkende direkt zu dem spezifischen Repository für das Projekt gehen, zu dem sie beitragen wollen. Bei einem Monorepo, das verschiedene Projekte hostet, müssen sich die Mitwirkenden jedoch erst in das richtige Projekt einfinden und verstehen, wie sich ihr Beitrag auf alle anderen Projekte auswirken kann.

Was ist Multi-Repo?

Der Multi-Repo-Ansatz verwendet mehrere Repositories, um die verschiedenen Bibliotheken oder Dienste eines Projekts zu hosten, das von einer Firma entwickelt wird. Im Extremfall hostet es jedes minimale Set an wiederverwendbarem Code oder eigenständiger Funktionalität (wie z.B. einen Microservice) unter seinem Repository.

Vorteile von Multi-Repo

Das Hosten jeder Bibliothek unabhängig von allen anderen bietet eine Fülle von Vorteilen.

Unabhängige Bibliotheksversionierung

Beim Taggen eines Repositories wird dessen gesamte Codebase mit dem „new“ Tag versehen. Da sich nur der Code einer bestimmten Bibliothek im Repository befindet, kann die Bibliothek unabhängig von allen anderen Bibliotheken, die anderswo gehostet werden, getaggt und versioniert werden.

Eine unabhängige Version für jede Bibliothek zu haben, hilft dabei, den Abhängigkeitsbaum für die Anwendung zu definieren, was es uns erlaubt, zu konfigurieren, welche Version jeder Bibliothek verwendet werden soll.

Unabhängige Service Releases

Da das Repository nur den Code für einen Dienst enthält und sonst nichts, kann es seinen eigenen Bereitstellungszyklus haben, unabhängig von den Fortschritten der Anwendungen, die auf es zugreifen.

Der Dienst kann einen schnellen Release-Zyklus verwenden, wie z.B. Continuous Delivery (wo neuer Code eingesetzt wird, nachdem er alle Tests bestanden hat). Einige Bibliotheken, die auf den Service zugreifen, können einen langsameren Release-Zyklus verwenden, wie z.B. solche, die nur einmal in der Woche ein neues Release produzieren.

Hilft bei der Definition der Zugriffskontrolle in der gesamten Organisation

Nur die Teammitglieder, die an der Entwicklung einer Bibliothek beteiligt sind, müssen zum entsprechenden Repository hinzugefügt werden und den Code herunterladen. Als Ergebnis gibt es eine implizite Zugriffskontrollstrategie für jede Schicht in der Anwendung. Diejenigen, die mit der Bibliothek zu tun haben, erhalten Bearbeitungsrechte und alle anderen dürfen keinen Zugriff auf das Repository erhalten. Oder sie erhalten Leserechte, aber keine Editierrechte.

Ermöglicht Teams autonomes Arbeiten

Die Teammitglieder können die Architektur der Bibliothek entwerfen und den Code implementieren, während sie isoliert von allen anderen Teams arbeiten. Sie können Entscheidungen treffen, die darauf basieren, was die Bibliothek im allgemeinen Kontext tut, ohne von den spezifischen Anforderungen eines externen Teams oder einer Anwendung beeinflusst zu werden.

Probleme mit dem Multi-Repo-Ansatz

Die Verwendung von mehreren Repositories kann zu verschiedenen Problemen führen.

Bibliotheken müssen ständig neu synchronisiert werden

Wenn eine neue Version einer Bibliothek veröffentlicht wird, die bahnbrechende Änderungen enthält, müssen Bibliotheken, die von dieser Bibliothek abhängig sind, angepasst werden, um die neueste Version zu verwenden. Wenn der Release-Zyklus der Bibliothek schneller ist als der ihrer abhängigen Bibliotheken, können sie schnell aus dem Takt geraten.

Die Teams müssen dann ständig aufholen, um die neuesten Versionen der anderen Teams zu nutzen. Da verschiedene Teams unterschiedliche Prioritäten haben, kann sich dies manchmal als mühsam erweisen.

Infolgedessen kann ein Team, das nicht in der Lage ist, den Rückstand aufzuholen, an der veralteten Version der Bibliothek, auf die es angewiesen ist, festhalten. Dieses Ergebnis wird Auswirkungen auf die Anwendung haben (in Bezug auf Sicherheit, Geschwindigkeit und andere Überlegungen), und die Lücke in der Entwicklung zwischen den Bibliotheken kann nur noch größer werden.

Kann Teams fragmentieren

Wenn verschiedene Teams nicht interagieren müssen, können sie in ihren eigenen Silos arbeiten. Langfristig kann dies dazu führen, dass Teams ihre eigenen Subkulturen innerhalb des Unternehmens bilden, wie z.B. unterschiedliche Programmier- oder Managementmethoden oder unterschiedliche Entwicklungstools.

Wenn ein Teammitglied irgendwann in einem anderen Team arbeiten muss, erleidet es vielleicht einen kleinen Kulturschock und muss eine neue Art und Weise lernen, seine Arbeit zu erledigen.

Monorepo vs. Multi-Repo: Primäre Unterschiede

Beide Ansätze beschäftigen sich letztlich mit dem gleichen Ziel: der Verwaltung der Codebase. Daher müssen beide die gleichen Herausforderungen lösen, einschließlich des Release-Managements, der Förderung der Zusammenarbeit zwischen den Teammitgliedern, dem Umgang mit Problemen, der Durchführung von Tests und anderen.

Der Hauptunterschied besteht darin, dass die Teammitglieder Entscheidungen treffen müssen: Entweder im Voraus für Monorepo oder im Nachhinein für Multi-Repo.

Lass uns diese Idee im Detail analysieren.

Da alle Bibliotheken im Multi-Repo unabhängig voneinander versioniert werden, kann ein Team, das eine Bibliothek mit brechenden Änderungen veröffentlicht, es sicher tun, indem es der neuesten Version eine neue Hauptversionsnummer zuweist. Andere Gruppen können ihre abhängigen Bibliotheken bei der alten Version bleiben lassen und auf die neue wechseln, sobald ihr Code angepasst wurde.

Dieser Ansatz überlässt die Entscheidung, wann alle anderen Bibliotheken angepasst werden sollen, jedem verantwortlichen Team, das es zu jeder Zeit tun kann. Wenn sie es zu spät tun und neue Bibliotheksversionen veröffentlicht werden, wird es immer schwieriger, die Lücke zwischen den Bibliotheken zu schließen.

Infolgedessen kann ein Team schnell und oft an seinem Code iterieren, während andere Teams nicht in der Lage sind, aufzuholen, was letztendlich zu divergierenden Bibliotheken führt.

Auf der anderen Seite können wir in einer Monorepo-Umgebung keine neue Version einer Bibliothek veröffentlichen, die eine andere Bibliothek kaputt macht, da deren Tests fehlschlagen werden. In diesem Fall muss das erste Team mit dem zweiten Team kommunizieren, um die Änderungen einzuarbeiten.

Dieser Ansatz zwingt die Teams dazu, alle Bibliotheken zusammen anzupassen, wenn eine Änderung für eine einzelne Bibliothek passieren muss. Alle Teams sind gezwungen, miteinander zu reden und gemeinsam eine Lösung zu finden.

Das Ergebnis ist, dass das erste Team nicht so schnell iterieren kann, wie es möchte, aber der Code über verschiedene Bibliotheken hinweg wird zu keinem Zeitpunkt anfangen, auseinanderzulaufen.

Zusammenfassend lässt sich sagen, dass der Multi-Repo-Ansatz dazu beitragen kann, eine Kultur des „move fast and break things“ unter den Teams zu schaffen, in der flinke unabhängige Teams ihren Output in ihrer Geschwindigkeit produzieren können. Stattdessen begünstigt der Monorepo-Ansatz eine Kultur der Achtsamkeit und Sorgfalt, in der Teams nicht mit einem Problem allein gelassen werden sollten.

Hybrider Poly-As-Mono-Ansatz

Wenn wir uns nicht entscheiden können, ob wir entweder den Multi-Repo oder den Monorepo-Ansatz verwenden sollen, gibt es auch den Zwischending-Ansatz: mehrere Repositories zu verwenden und ein Tool einzusetzen, um sie synchron zu halten, so dass es einem Monorepo ähnelt, aber mit mehr Flexibilität.

Meta ist ein solches Tool. Es organisiert mehrere Repositories in Unterverzeichnissen und bietet eine Kommandozeilenschnittstelle, die den gleichen Befehl auf allen gleichzeitig ausführt.

Ein Meta-Repository enthält die Informationen darüber, welche Repositories ein Projekt ausmachen. Das Klonen dieses Repositories via Meta klont dann rekursiv alle benötigten Repositories, was es für neue Teammitglieder einfacher macht, sofort mit der Arbeit an ihren Projekten zu beginnen.

Um ein Meta-Repository und alle seine definierten multiplen Repos zu klonen, müssen wir folgendes ausführen:

meta git clone [meta repo url]

Meta wird für jedes Repository einen git clone ausführen und es in einem Unterordner ablegen:

Klonen eines Meta-Projekts.
Klonen eines Meta-Projekts. (Bildquelle: github.com/mateodelnorte/meta)

Von da an wird die Ausführung des meta exec Befehls den Befehl in jedem Unterordner ausführen. Zum Beispiel wird git checkout master auf jedem Repository wie folgt ausgeführt:

meta exec "git checkout master"

Hybrider Mono-As-Poly-Ansatz

Ein anderer Ansatz ist die Verwaltung des Codes über ein Monorepo für die Entwicklung, aber das Kopieren des Codes jeder Bibliothek in ein unabhängiges Repository für die Bereitstellung.

Diese Strategie ist im PHP-Ökosystem weit verbreitet, da Packagist (das Haupt-Repository von Composer) eine öffentliche Repository-URL benötigt, um ein Paket zu veröffentlichen, und es nicht möglich ist, anzugeben, dass sich das Paket in einem Unterverzeichnis des Repositorys befindet.

In Anbetracht der Packagist-Beschränkung können PHP-Projekte immer noch ein Monorepo für die Entwicklung verwenden, aber sie müssen den Multi-Repo-Ansatz für die Bereitstellung nutzen.

Um diese Conversion zu erreichen, können wir ein Skript mit git subtree split ausführen oder eines der verfügbaren Tools verwenden, die die gleiche Logik ausführen:

Wer nutzt Monorepo vs. Multi-Repo

Einige große Tech-Unternehmen favorisieren den Monorepo-Ansatz, während andere sich für die Multi-Repo-Methode entschieden haben.

Google, Facebook, Twitter und Uber haben sich alle öffentlich für den Monorepo-Ansatz verbürgt. Microsoft betreibt den größten Git Monorepo auf dem Planeten, um den Quellcode des Windows Betriebssystems zu hosten.

Auf der gegenüberliegenden Seite sind Netflix, Amazon und Lyft berühmte Unternehmen, die den Multi-Repo-Ansatz verwenden.

Auf der hybriden poly-as-mono Seite aktualisiert Android mehrere Repositories, die wie ein Monorepo verwaltet werden.

Auf der hybriden Mono-als-Poly-Seite hält Symfony den Code für alle seine Komponenten in einem Monorepo. Für das Deployment teilen sie es in unabhängige Repositories auf (wie symfony/dependency-injection und symfony/event-dispatcher.)

Beispiele für Monorepo und Multi-Repo

Der WordPress-Account auf GitHub hostet Beispiele sowohl für den Monorepo als auch für den Multi-Repo-Ansatz.

Gutenberg, der WordPress-Blockeditor, besteht aus mehreren Dutzend JavaScript-Paketen. Diese Pakete werden alle auf dem WordPress/gutenberg-Monorepo gehostet und über Lerna verwaltet, um sie im npm-Repository zu veröffentlichen.

Openverse, die Suchmaschine für offen lizenzierte Medien, hostet seine Hauptbestandteile in unabhängigen Repositories: Front-End, Catalog und API.

Monorepo vs. Multi-Repo: Wie soll man sich entscheiden?

Wie bei vielen Entwicklungsproblemen gibt es keine vordefinierte Antwort darauf, welchen Ansatz du verwenden solltest. Verschiedene Unternehmen und Projekte werden von der einen oder anderen Strategie profitieren, basierend auf ihren einzigartigen Bedingungen, wie z.B.:

  • Wie groß ist die Codebase? Enthält sie Gigabytes an Daten?
  • Wie viele Leute werden an der Codebasis arbeiten? Sind es etwa 10, 100 oder 1.000?
  • Wie viele Pakete wird es geben? Sind es etwa 10, 100 oder 1.000?
  • An wie vielen Paketen muss das Team zu einer bestimmten Zeit arbeiten?
  • Wie eng gekoppelt sind die Pakete?
  • Sind verschiedene Programmiersprachen beteiligt? Benötigen sie eine bestimmte installierte Software oder spezielle Hardware, um zu laufen?
  • Wie viele Deployment Tools werden benötigt, und wie komplex sind sie einzurichten?
  • Wie ist die Kultur im Unternehmen? Werden die Teams ermutigt, zusammenzuarbeiten?
  • Mit welchen Tools und Technologien können die Teams umgehen?

Zusammenfassung

Es gibt zwei Hauptstrategien für das Hosten und Verwalten von Code: Monorepo vs. Multi-Repo. Der Monorepo-Ansatz beinhaltet die Speicherung des Codes für verschiedene Bibliotheken oder Projekte – und sogar den gesamten Code eines Unternehmens – in einem einzigen Repository. Und das Multi-Repo-System unterteilt den Code in Einheiten, wie Bibliotheken oder Dienste, und hält deren Code in unabhängigen Repositories gehostet.

Welcher Ansatz zu verwenden ist, hängt von einer Vielzahl von Bedingungen ab. Beide Strategien haben verschiedene Vor- und Nachteile, die wir in diesem Artikel im Detail behandelt haben.

Hast du noch Fragen zu Monorepos oder Multi-Repos? Lass es uns in den Kommentaren wissen!

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.