Monorepository mit Go: Code-Organisation in der Praxis
\n\nEin Monorepository (Monorepo) ist ein Ansatz zur Verwaltung von Quellcode, bei dem alle Projekte, Bibliotheken und Dienste in einem einzigen Repository gespeichert werden. In der Go-Welt ist dieser Ansatz dank der integrierten Modulunterstützung und des flexiblen Paketsystems besonders beliebt geworden. In diesem Artikel werden wir uns ansehen, wie man ein Monorepository in Go in der Praxis organisiert, eine typische Struktur, Codebeispiele und Best Practices betrachten.
\n\nWarum ein Monorepository in Go?
\n\nEin Monorepository löst mehrere zentrale Aufgaben:
\n- \n
- Vereinfachung der Abhängigkeitsverwaltung — alle internen Pakete befinden sich an einem Ort, sie lassen sich leicht aktualisieren und refaktorieren. \n
- Einheitliche Änderungshistorie — atomare Commits, die mehrere Dienste gleichzeitig betreffen, funktionieren ohne zusätzliche Synchronisation. \n
- Schnellere CI/CD — Pipelines können so konfiguriert werden, dass sie nur die geänderten Teile des Projekts erstellen. \n
- Höhere Codequalität — einheitliche Standards, Linter und Code-Reviews lassen sich leichter durchsetzen. \n
Struktur eines Go-Monorepositorys: Grundlegendes Schema
\n\nBetrachten wir eine typische Struktur eines Go-Monorepositorys. Als Beispiel nehmen wir ein Projekt, das aus zwei Mikrodiensten (api und worker) und einer gemeinsamen Bibliothek lib besteht:
my-monorepo/\n├── go.work\n├── go.work.sum\n├── apps/\n│ ├── api/\n│ │ ├── go.mod\n│ │ ├── main.go\n│ │ └── handler.go\n│ └── worker/\n│ ├── go.mod\n│ ├── main.go\n│ └── process.go\n├── lib/\n│ ├── go.mod\n│ ├── auth/\n│ │ └── auth.go\n│ └── db/\n│ └── db.go\n├── scripts/\n│ ├── build.sh\n│ └── test.sh\n└── Makefile\n\nSchlüsselelemente:
\n- \n
go.work— die Go-Workspace-Datei, die alle Module miteinander verbindet. \n apps/— Verzeichnis mit Anwendungen (Mikrodienste, CLI-Tools usw.). \n lib/— gemeinsame Bibliotheken, die von allen Anwendungen genutzt werden. \n scripts/— Hilfsskripte für Build, Tests und Deployment. \n
Einrichtung des Go Workspace (go.work)
\n\nGo 1.18+ bietet den Mechanismus workspace, der es ermöglicht, mit mehreren Modulen gleichzeitig zu arbeiten. Erstellen wir die Datei go.work im Stammverzeichnis:
go 1.22\n\nuse (\n ./apps/api\n ./apps/worker\n ./lib\n)\n\nJetzt können während der Entwicklung Befehle aus dem Stammverzeichnis des Projekts ausgeführt werden, und Go sieht alle Module als eine Einheit. Um beispielsweise Tests für alle Module auszuführen:
\n\ngo test ./...\n\nUnd um einen bestimmten Dienst zu erstellen:
\n\ncd apps/api && go build -o bin/api .\n\nCodebeispiel: Gemeinsame Bibliothek und Mikrodienst
\n\nErstellen wir eine einfache Authentifizierungsbibliothek in lib/auth/auth.go:
package auth\n\nimport "errors"\n\nvar (\n ErrInvalidToken = errors.New("invalid token")\n)\n\n// ValidateToken prüft das Token und gibt die Benutzer-ID zurück.\nfunc ValidateToken(token string) (string, error) {\n if token == "" {\n return "", ErrInvalidToken\n }\n // Hier könnte eine echte JWT-Überprüfung oder ein Aufruf eines externen Dienstes erfolgen\n return "user-123", nil\n}\n\nJetzt verwenden wir diese Bibliothek im Mikrodienst apps/api/handler.go:
package main\n\nimport (\n "encoding/json"\n "net/http"\n "my-monorepo/lib/auth"\n)\n\ntype UserResponse struct {\n UserID string `json:"user_id"`\n}\n\nfunc UserHandler(w http.ResponseWriter, r *http.Request) {\n token := r.Header.Get("Authorization")\n userID, err := auth.ValidateToken(token)\n if err != nil {\n http.Error(w, "Unauthorized", http.StatusUnauthorized)\n return\n }\n\n resp := UserResponse{UserID: user