Die eigenwillige Struktur von Go-Projekten
Emily Parker
Product Engineer · Leapcell

Einleitung
Im lebendigen Go-Ökosystem ist das Thema Projektstruktur oft Anlass für angeregte Diskussionen. Für viele Neulinge und sogar erfahrene Entwickler ist das Repository „Standard Go Project Layout“ zu einem De-facto-Referenzpunkt geworden. Es schreibt eine umfassende Verzeichnisstruktur vor, die darauf abzielt, eine gemeinsame Grundlage für große, vielschichtige Go-Projekte zu bieten. Diese Initiative, obwohl sie lobenswerterweise versucht, Best Practices zu standardisieren, präsentiert einen Bauplan, der sich in der Praxis für die überwiegende Mehrheit der Webanwendungen als übertrieben oder sogar kontraproduktiv erweisen kann. Dieser Artikel befasst sich damit, warum die blinde Übernahme dieses „Standard“-Layouts die Einfachheit und Agilität beeinträchtigen kann, die Go fördert, insbesondere beim Erstellen von Webdiensten. Stattdessen werden wir alternative, passendere Ansätze untersuchen, die besser mit der iterativen Natur und dem typischen Umfang der Webentwicklung übereinstimmen.
Go-Projektstrukturen entschlüsseln
Bevor wir tiefer eintauchen, wollen wir ein gemeinsames Verständnis der Kernkonzepte schaffen, die wir diskutieren werden.
Das „Standard Go Project Layout“
Dieses weithin zitierte Repository schlägt eine sehr eigenwillige, hierarchische Struktur vor. Sein Hauptziel ist es, ein Projekt in verschiedene Verantwortlichkeiten zu unterteilen:
cmd/: Enthält Hauptanwendungen für das Projekt. Jedes Verzeichnis untercmdist eine separate ausführbare Datei.pkg/: Bibliothekscode, der für die allgemeine Verwendung durch andere Projekte bestimmt ist.internal/: Privater Anwendungs- und Bibliothekscode, der von anderen Projekten nicht importiert werden kann.api/: API-Definitionen (z. B. OpenAPI, Protobuf).web/: Webanwendungsspezifische Komponenten (z. B. statische Assets, Vorlagen).configs/: Konfigurationsiledaten-Vorlagen oder Standardkonfigurationen.build/: Verpackung und kontinuierliche Integration.deployments/: Bereitstellungskonfigurationen für IaaS, PaaS und Container-Orchestrierungen.scripts/: Skripte zur Ausführung verschiedener Build-, Installations-, Analyse- oder anderer Verwaltungsaufgaben.test/: Externe Testanwendungen und zusätzliche Testdaten.
Die Begründung für dieses Layout ist, eine umfassende, unternehmenstaugliche Struktur bereitzustellen, die mit der Komplexität des Projekts skaliert, insbesondere für Monorepos oder Projekte mit mehreren separaten ausführbaren Dateien und gemeinsam genutzten internen Bibliotheken.
Warum es möglicherweise nicht gut für Webanwendungen geeignet ist
Viele Webanwendungen, insbesondere solche, die modernen Microservices- oder API-First-Paradigmen folgen, sind oft stärker fokussiert. Sie dienen typischerweise einem einzigen Hauptzweck, z. B. der Verarbeitung von HTTP-Anfragen, der Interaktion mit einer Datenbank und möglicherweise der Kommunikation mit einigen externen Diensten. Lassen Sie uns spezifische Gründe untersuchen, warum das „Standard Go Project Layout“ zu einer Belastung werden kann.
1. Überarchitektur für Einfachheit
Go's Philosophie, oft als „weniger ist mehr“ zusammengefasst, fördert einfache, explizite Designs. Eine komplexe Verzeichnisstruktur erhöht den kognitiven Aufwand. Für eine typische Web-API, die ein oder zwei Hauptausführbare Dateien (cmd/api, cmd/worker) haben könnte, kann die Trennung von cmd, pkg und internal erzwungen wirken.
Betrachten Sie einen einfachen Webdienst:
// main.go package main import ( "log" "net/http" ) func helloHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, world!")) } func main() { http.HandleFunc("/", helloHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }
Diese in cmd/api/main.go zu platzieren, mit potenziell leeren oder spärlich gefüllten Verzeichnissen pkg und internal, erscheint unverhältnismäßig zum tatsächlichen Code. Der Wert von pkg und internal zeigt sich, wenn Sie wiederverwendbare Komponenten haben, die tatsächlich von mehreren, separaten Anwendungen innerhalb desselben Repositorys gemeinsam genutzt werden, oder wenn Sie externe Importe bestimmter Pakete explizit verhindern möchten. Für einen einzelnen Webdienst fügt die „interne“ Unterscheidung hauptsächlich eine Indirektionsschicht Ihrer eigenen Projektstruktur hinzu.
2. Die Debatte um das internal-Paket
Das Verzeichnis internal ist ein Kernstück des „Standard Layouts“. Go's Compiler erzwingt, dass Pakete in einem internal-Verzeichnis nicht außerhalb des Moduls, in dem internal residiert, importiert werden können. Dies ist ein mächtiges Merkmal zur Erzwingung von Modularität. Für einen in sich geschlossenen Webdienst ist jedoch fast die gesamte Anwendungslogik intern für diesen Dienst. Das Platzieren jedes Domänenmodells, Service-Handlers und Repositories in separaten internal-Unterverzeichnissen fragmentiert oft die Codebasis, ohne dass dies bei kleinen bis mittleren Projekten einen klaren Vorteil bringt.
Lassen Sie uns dies mit einer gängigen Webanwendungsstruktur veranschaulichen:
Ansatz mit Standard-Layout:
my-webapp/
├── cmd/
│ └── api/
│ └── main.go
├── internal/
│ ├── auth/
│ │ └── service.go
│ ├── handlers/
│ │ └── user.go
│ ├── models/
│ │ └── user.go
│ └── repository/
│ └── user.go
└── go.mod
Hier würde cmd/api/main.go internal/auth, internal/handlers usw. importieren. Obwohl funktional korrekt, wirkt das Präfix internal eher als organisatorische Konvention denn als Notwendigkeit, externe Importe in einem einzelnen Dienstprojekt zu verhindern.
Alternativer (flacherer) Ansatz:
my-webapp/
├── main.go
├── auth/
│ └── service.go
├── handlers/
│ └── user.go
├── models/
│ └── user.go
├── repository/
│ └── user.go
└── go.mod
In dieser flacheren Struktur sind die Module (auth, handlers, models, repository) direkt im Projektstammverzeichnis angesiedelt. Obwohl sie von anderen Modulen importiert werden können (wenn dies eine Bibliothek wäre), ist dies für eine eigenständige Anwendung normalerweise kein Problem. Die Go-Community bezeichnet dies oft als „flaches“ Layout oder „Paket-nach-Funktion“-Layout. Es nutzt Go's starke Paketgrenzen, um Schnittstellen zu definieren und Implementierungsdetails zu verbergen, ohne die zusätzliche internal-Ebene.
3. Vorzeitige Optimierung der Projektorganisation
Die Übernahme einer komplexen Struktur von Anfang an kann eine Form der vorzeitigen Optimierung sein. Sie kann Probleme lösen, die für Ihr Projekt noch nicht existieren. Wenn sich Ihre Webanwendung weiterentwickelt, sind Refactoring und Neugestaltung von Code natürliche Bestandteile des Entwicklungsprozesses. Der Beginn mit einer einfacheren Struktur ermöglicht organisches Wachstum und Anpassung.
Zum Beispiel, wenn Sie anfangs ein handlers-Paket für alle Ihre HTTP-Handler haben:
my-webapp/
├── main.go
├── handlers/
│ ├── user.go
│ └── product.go
└── go.mod
Wenn das Projekt wächst und user.go und product.go zu groß werden, können Sie sich entscheiden, nach Funktion zu gruppieren:
my-webapp/
├── main.go
├── user/
│ ├── handler.go
│ └── service.go
├── product/
│ ├── handler.go
│ └── service.go
└── go.mod
Diese Entwicklung ist einfacher zu verwalten, wenn Sie nicht ständig darüber diskutieren müssen, ob ein neues Paket unter pkg, internal oder einem neuen cmd-Eintrag platziert werden soll.
4. Der „Standard“ impliziert Autorität
Der Titel selbst, „Standard Go Project Layout“, hat ein erhebliches Gewicht. Neulinge könnten ihn als den einzigen richtigen Weg zur Strukturierung eines Go-Projekts wahrnehmen, was zu unnötiger Komplexität in einfachen Webdiensten führt. Das Go-Team selbst (die Schöpfer der Sprache) befürwortet keine offizielle Projektstruktur und überlässt dies lieber einzelnen Projekten. Die Organisation golang-standards wird von der Community getragen und ist kein offizielles Go-Projekt. Diese Unterscheidung ist entscheidend.
Ein pragmatischerer Ansatz für Webanwendungen
Viele erfolgreiche Go-Webanwendungen setzen auf einfachere, pragmatischere Layouts. Gängige Muster sind:
-
Flache Struktur / Paket-nach-Funktion: Gruppierung von zusammengehörigem Code in Pakete der obersten Ebene, die oft nach Funktionen oder Domänenkonzepten benannt sind.
my-webapp/ ├── main.go # Einstiegspunkt ├── config/ # Anwendungskonfiguration ├── server/ # HTTP-Server-Einrichtung, Middleware ├── user/ # Benutzerbezogene Domäne, Dienst, Handler, Repository │ ├── handler.go │ ├── service.go │ └── repository.go ├── product/ # Produktbezogene Domäne, Dienst, Handler, Repository │ ├── handler.go │ └── service.go ├── common/ # Hilfsfunktionen, gemeinsame Schnittstellen └── go.modDieser Ansatz bündelt den relevanten Code für eine Funktion zusammen, was die Auffindbarkeit und Kohäsion verbessert.
main.goverdrahtet alles zusammen. -
Schichtweise / Hexagonal (Ports & Adapter) innerhalb einer flachen Struktur: Für größere Webanwendungen, die Anliegen strenger trennen möchten, kann eine Schichtarchitektur innerhalb einer flacheren Struktur implementiert werden.
my-webapp/ ├── main.go ├── config/ ├── internal/ # Anwendungsspezifische Kernlogik (Domäne, Anwendungsfälle, Dienste) │ ├── domain/ │ │ ├── user.go │ │ └── product.go │ ├── service/ # Geschäftslogik, Orchestrierung von Domänenobjekten │ │ ├── user.go │ │ └── product.go │ └── ports/ # Schnittstellen für externe Abhängigkeiten (Datenbank, HTTP, Nachrichtenwarteschlangen) │ ├── database.go │ └── http.go ├── adapters/ # Implementierungen der Ports (z. B. GORM für Datenbank, Gin/Echo für HTTP) │ ├── http/ │ │ └── handler.go │ └── persistence/ │ └── postgres.go └── go.modHier wird das Verzeichnis
internalfür die Kernanwendungslogik verwendet, die nicht exponiert werden darf oder keine direkten externen Abhängigkeiten haben sollte.adaptersverbinden dann diesen Kern mit der Außenwelt. Dies ist eine weitaus spezifischere und sinnvollere Verwendung voninternal.
Der Schlüssel ist die Auswahl einer Struktur, die am besten mit dem aktuellen Umfang und der Komplexität Ihres Projekts übereinstimmt und dessen natürliche Weiterentwicklung ermöglicht. Beginnen Sie einfach und führen Sie Komplexität erst ein, wenn sie tatsächlich ein Problem löst, auf das Sie stoßen.
Fazit
Während das „Standard Go Project Layout“ eine umfassende, wenn auch eigenwillige Struktur bietet, die für komplexe, Multi-Executable-Projekte oder Monorepos geeignet ist, führt es für typische Webanwendungen oft zu unnötiger Komplexität. Go's Stärken liegen in seiner Einfachheit und seinem expliziten Design; eine Projektstruktur sollte diese Werte widerspiegeln. Für die meisten Webdienste wird ein flacherer, funktionsorientierter oder ein wohlüberlegter schichtweiser Ansatz innerhalb einer kompakteren Struktur die Klarheit erhöhen, die kognitive Belastung reduzieren und eine agilere Entwicklung fördern. Ihre Projektstruktur sollte Ihnen dienen, nicht umgekehrt.

