Go-Projektstruktur: Praktiken und Muster

Strukturieren Sie Ihre Go-Projekte für Skalierbarkeit und Klarheit

Inhaltsverzeichnis

Die effektive Strukturierung eines Go-Projekts ist grundlegend für langfristige Wartbarkeit, Teamzusammenarbeit und Skalierbarkeit. Im Gegensatz zu Frameworks, die starre Verzeichnisstrukturen erzwingen, setzt Go auf Flexibilität – aber mit dieser Freiheit kommt die Verantwortung, Muster zu wählen, die den spezifischen Bedürfnissen Ihres Projekts dienen.

Projektbaum

Verständnis der Go-Philosophie zur Projektstruktur

Go’s minimalistische Designphilosophie erstreckt sich auf die Projektorganisation. Die Sprache schreibt keine bestimmte Struktur vor, sondern vertraut darauf, dass Entwickler fundierte Entscheidungen treffen. Dieser Ansatz hat die Community dazu veranlasst, mehrere bewährte Muster zu entwickeln, von einfachen flachen Layouts für kleine Projekte bis hin zu komplexen Architekturen für Unternehmenssysteme.

Das zentrale Prinzip lautet Einfachheit zuerst, Komplexität nur wenn nötig. Viele Entwickler fallen in die Falle, ihre anfängliche Struktur überzuteuern, indem sie tief verschachtelte Verzeichnisse und vorzeitige Abstraktionen erstellen. Beginnen Sie mit dem, was Sie heute benötigen, und refaktorieren Sie, wenn Ihr Projekt wächst.

Wann sollte ich das internal/-Verzeichnis gegenüber pkg/ verwenden?

Das internal/-Verzeichnis dient einem speziellen Zweck in Go’s Design: Es enthält Pakete, die von externen Projekten nicht importiert werden können. Go’s Compiler erzwingt diese Einschränkung, wodurch internal/ ideal für private Anwendungslogik, Geschäftsregeln und Utilities ist, die nicht außerhalb Ihres Projekts wiederverwendet werden sollen.

Das pkg/-Verzeichnis signalisiert dagegen, dass der Code für die externe Nutzung bestimmt ist. Platzieren Sie Code hier nur, wenn Sie eine Bibliothek oder wiederverwendbare Komponenten erstellen, die andere importieren sollen. Viele Projekte benötigen pkg/ überhaupt nicht – wenn Ihr Code nicht extern genutzt wird, ist es sauberer, ihn an der Wurzel oder in internal/ zu halten. Beim Erstellen wiederverwendbarer Bibliotheken sollten Sie Go-Generics nutzen, um typsichere, wiederverwendbare Komponenten zu erstellen.

Die Standard-Go-Projektstruktur

Das am weitesten verbreitete Muster ist die golang-standards/project-layout, wobei zu beachten ist, dass dies kein offizieller Standard ist. Hier ist, wie eine typische Struktur aussieht:

myproject/
├── cmd/
│   ├── api/
│   │   └── main.go
│   └── worker/
│       └── main.go
├── internal/
│   ├── auth/
│   ├── storage/
│   └── transport/
├── pkg/
│   ├── logger/
│   └── crypto/
├── api/
│   └── openapi.yaml
├── config/
│   └── config.yaml
├── scripts/
│   └── deploy.sh
├── go.mod
├── go.sum
└── README.md

Das cmd/-Verzeichnis

Das cmd/-Verzeichnis enthält die Einstiegspunkte Ihrer Anwendung. Jedes Unterverzeichnis stellt eine separate ausführbare Binärdatei dar. Zum Beispiel baut cmd/api/main.go Ihren API-Server, während cmd/worker/main.go möglicherweise einen Hintergrundaufgabenprozessor erstellt.

Beste Praxis: Halten Sie Ihre main.go-Dateien minimal – gerade genug, um Abhängigkeiten zu verbinden, Konfigurationen zu laden und die Anwendung zu starten. Alle wesentliche Logik gehört in Pakete, die main.go importiert.

// cmd/api/main.go
package main

import (
    "log"
    "myproject/internal/server"
    "myproject/internal/config"
)

func main() {
    cfg, err := config.Load()
    if err != nil {
        log.Fatal(err)
    }

    srv := server.New(cfg)
    if err := srv.Start(); err != nil {
        log.Fatal(err)
    }
}

Das internal/-Verzeichnis

Hier befindet sich Ihr privater Anwendungscode. Der Go-Compiler verhindert, dass externe Projekte Pakete innerhalb von internal/ importieren, wodurch es ideal ist für:

  • Geschäftslogik und Domänenmodelle
  • Anwendungsdienste
  • Interne APIs und Schnittstellen
  • Datenbankrepositories (für die Wahl des richtigen ORMs, siehe unseren Vergleich von Go-ORMs für PostgreSQL)
  • Authentifizierungs- und Autorisierungslogik

Organisieren Sie internal/ nach Funktionen oder Domänen, nicht nach technischen Schichten. Anstatt internal/handlers/, internal/services/, internal/repositories/ bevorzugen Sie internal/user/, internal/order/, internal/payment/, wobei jedes Paket seine Handler, Dienste und Repositories enthält.

Sollte ich mit einer komplexen Verzeichnisstruktur beginnen?

Auf keinen Fall. Wenn Sie ein kleines Werkzeug oder einen Prototypen erstellen, beginnen Sie mit:

myproject/
├── main.go
├── go.mod
└── go.sum

Wenn Ihr Projekt wächst und Sie logische Gruppierungen identifizieren, fügen Sie Verzeichnisse hinzu. Sie könnten ein db/-Paket hinzufügen, wenn die Datenbanklogik bedeutend wird, oder ein api/-Paket, wenn sich HTTP-Handler vermehren. Lassen Sie die Struktur natürlich entstehen, anstatt sie vorab aufzuerlegen.

Flache vs. verschachtelte Strukturen: Der richtige Ausgleich

Einer der häufigsten Fehler in der Go-Projektstruktur ist übermäßige Verschachtelung. Go bevorzugt flache Hierarchien – typischerweise ein oder zwei Ebenen tief. Tiefe Verschachtelung erhöht die kognitive Belastung und macht Importe umständlich.

Was sind die häufigsten Fehler?

Übermäßige Verzeichnisverschachtelung: Vermeiden Sie Strukturen wie internal/services/user/handlers/http/v1/. Dies schafft unnötige Navigationskomplexität. Verwenden Sie stattdessen internal/user/handler.go.

Generische Paketnamen: Namen wie utils, helpers, common oder base sind Code-Gerüche. Sie vermitteln keine spezifische Funktionalität und werden oft zu Sammelbecken für unzusammenhängenden Code. Verwenden Sie beschreibende Namen: validator, auth, storage, cache.

Zirkuläre Abhängigkeiten: Wenn Paket A Paket B importiert und B wiederum A importiert, haben Sie eine zirkuläre Abhängigkeit – einen Kompilierungsfehler in Go. Dies deutet typischerweise auf eine schlechte Trennung von Verantwortlichkeiten hin. Führen Sie Schnittstellen ein oder extrahieren Sie gemeinsame Typen in ein separates Paket.

Vermischung von Verantwortlichkeiten: Halten Sie HTTP-Handler auf HTTP-relevante Aspekte beschränkt, Datenbankrepositories auf den Datenzugriff und die Geschäftslogik in Service-Paketen. Die Platzierung von Geschäftsregeln in Handlern erschwert das Testen und koppelt Ihre Domänenlogik an HTTP.

Domain-Driven Design und Hexagonale Architektur

Für größere Anwendungen, insbesondere Microservices, bietet Domain-Driven Design (DDD) mit hexagonaler Architektur eine klare Trennung von Verantwortlichkeiten.

Wie strukturiere ich einen Microservice nach Domain-Driven Design?

Die hexagonale Architektur organisiert den Code in konzentrischen Schichten mit nach innen gerichteten Abhängigkeiten:

internal/
├── domain/
│   └── user/
│       ├── entity.go        # Domänenmodelle und Wertobjekte
│       ├── repository.go    # Repository-Schnittstelle (Port)
│       └── service.go       # Domänendienste
├── application/
│   └── user/
│       ├── create_user.go   # Use Case: Benutzer erstellen
│       ├── get_user.go      # Use Case: Benutzer abrufen
│       └── service.go       # Anwendungsdienst-Orchestrierung
├── adapter/
│   ├── http/
│   │   └── user_handler.go  # HTTP-Adapter (REST-Endpunkte)
│   ├── postgres/
│   │   └── user_repo.go     # Datenbank-Adapter (implementiert Repository-Port)
│   └── redis/
│       └── cache.go         # Cache-Adapter
└── api/
    └── http/
        └── router.go        # Routenkonfiguration und Middleware

Domänenschicht (domain/): Kern-Geschäftslogik, Entitäten, Wertobjekte und Domänendienst-Schnittstellen. Diese Schicht hat keine Abhängigkeiten zu externen Systemen – keine HTTP-, keine Datenbank-Imports. Sie definiert Repository-Schnittstellen (Ports), die Adapter implementieren.

Anwendungsschicht (application/): Use Cases, die Domänenobjekte orchestrieren. Jeder Use Case (z.B. “Benutzer erstellen”, “Zahlung verarbeiten”) ist eine separate Datei oder ein separates Paket. Diese Schicht koordiniert Domänenobjekte, enthält aber keine Geschäftsregeln selbst.

Adapterschicht (adapter/): Implementiert Schnittstellen, die von inneren Schichten definiert wurden. HTTP-Handler konvertieren Anfragen in Domänenobjekte, Datenbankrepositories implementieren Persistenz, Nachrichtenwarteschlangen verwalten asynchrone Kommunikation. Diese Schicht enthält alle framework-spezifischen und Infrastruktur-Code.

API-Schicht (api/): Routen, Middleware, DTOs (Data Transfer Objects), API-Versionierung und OpenAPI-Spezifikationen.

Diese Struktur stellt sicher, dass Ihre Kern-Geschäftslogik testbar bleibt und unabhängig von Frameworks, Datenbanken oder externen Diensten ist. Sie können PostgreSQL durch MongoDB ersetzen oder REST durch gRPC, ohne die Domänenlogik anzupassen.

Praktische Muster für verschiedene Projekttypen

Kleines CLI-Tool

Für Command-Line-Anwendungen möchten Sie eine Struktur, die mehrere Befehle und Unterbefehle unterstützt. Erwägen Sie die Verwendung von Frameworks wie Cobra für die Befehlsstruktur und Viper für die Konfigurationsverwaltung. Unser Leitfaden zu Erstellung von CLI-Anwendungen in Go mit Cobra & Viper behandelt dieses Muster ausführlich.

mytool/
├── main.go
├── command/
│   ├── root.go
│   └── version.go
├── go.mod
└── README.md

REST-API-Dienst

Beim Erstellen von REST-APIs in Go trennt diese Struktur die Verantwortlichkeiten sauber: Handler behandeln HTTP-relevante Aspekte, Dienste enthalten Geschäftslogik und Repositories verwalten den Datenzugriff. Für einen umfassenden Leitfaden, der Standardbibliotheksansätze, Frameworks, Authentifizierung, Testmuster und produktionsreife Best Practices abdeckt, siehe unseren vollständigen Leitfaden zur Erstellung von REST-APIs in Go.

myapi/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── config/
│   ├── middleware/
│   ├── user/
│   │   ├── handler.go
│   │   ├── service.go
│   │   └── repository.go
│   └── product/
│       ├── handler.go
│       ├── service.go
│       └── repository.go
├── pkg/
│   └── httputil/
├── go.mod
└── README.md

Monorepo mit mehreren Diensten

myproject/
├── cmd/
│   ├── api/
│   ├── worker/
│   └── scheduler/
├── internal/
│   ├── shared/        # Gemeinsam genutzte interne Pakete
│   ├── api/          # API-spezifischer Code
│   ├── worker/       # Worker-spezifischer Code
│   └── scheduler/    # Scheduler-spezifischer Code
├── pkg/              # Gemeinsam genutzte Bibliotheken
├── go.work           # Go-Workspace-Datei
└── README.md

Testen und Dokumentation

Platzieren Sie Testdateien neben dem Code, den sie testen, mit dem Suffix _test.go:

internal/
└── user/
    ├── service.go
    ├── service_test.go
    ├── repository.go
    └── repository_test.go

Diese Konvention hält Tests nah an der Implementierung, was sie einfach zu finden und zu warten macht. Für Integrationstests, die mehrere Pakete betreffen, erstellen Sie ein separates test/ Verzeichnis an der Projektwurzel. Für umfassende Anleitungen zum Schreiben effektiver Unit-Tests, einschließlich tabellengetriebener Tests, Mocks, Abdeckungsanalysen und Best Practices, sehen Sie unsere Anleitung zur Go Unit-Test-Struktur und Best Practices.

Dokumentation gehört in:

  • README.md: Projektübersicht, Einrichtungshinweise, grundlegende Verwendung
  • docs/: Ausführliche Dokumentation, Architekturentscheidungen, API-Referenzen
  • api/: OpenAPI/Swagger-Spezifikationen, Protobuf-Definitionen

Für REST-APIs ist die Generierung und Bereitstellung von OpenAPI-Dokumentation mit Swagger essenziell für die API-Entdeckbarkeit und die Entwicklererfahrung. Unsere Anleitung zum Hinzufügen von Swagger zu Ihrer Go-API behandelt die Integration mit beliebten Frameworks und Best Practices.

Verwaltung von Abhängigkeiten mit Go-Modulen

Jedes Go-Projekt sollte Go-Module für die Abhängigkeitsverwaltung verwenden. Für eine umfassende Referenz zu Go-Befehlen und Modulverwaltung, sehen Sie unseren Go-Cheatsheet. Initialisieren Sie mit:

go mod init github.com/yourusername/myproject

Dies erstellt go.mod (Abhängigkeiten und Versionen) und go.sum (Prüfsummen zur Verifizierung). Halten Sie diese Dateien in der Versionskontrolle für reproduzierbare Builds.

Aktualisieren Sie Abhängigkeiten regelmäßig:

go get -u ./...          # Aktualisiert alle Abhängigkeiten
go mod tidy              # Entfernt ungenutzte Abhängigkeiten
go mod verify            # Verifiziert Prüfsummen

Wichtige Erkenntnisse

  1. Einfach anfangen, natürlich entwickeln: Überengineern Sie Ihre anfängliche Struktur nicht. Fügen Sie Verzeichnisse und Pakete hinzu, wenn die Komplexität dies erfordert.

  2. Flache Hierarchien bevorzugen: Begrenzen Sie die Verschachtelung auf eine oder zwei Ebenen. Go’s flache Paketstruktur verbessert die Lesbarkeit.

  3. Beschreibende Paketnamen verwenden: Vermeiden Sie generische Namen wie utils. Benennen Sie Pakete nach ihrer Funktion: auth, storage, validator.

  4. Anforderungen klar trennen: Halten Sie Handler auf HTTP fokussiert, Repositories auf Datenzugriff und Geschäftslogik in Service-Paketen.

  5. Nutzen Sie internal/ für Privatsphäre: Verwenden Sie es für Code, der nicht extern importiert werden sollte. Die meisten Anwendungs-Codes gehören hierher.

  6. Wenden Sie Architektur-Muster an, wenn nötig: Für komplexe Systeme bieten Hexagonale Architektur und DDD klare Grenzen und Testbarkeit.

  7. Lassen Sie sich von Go leiten: Folgen Sie Go-Idiomen, anstatt Muster aus anderen Sprachen zu importieren. Go hat seine eigene Philosophie über Einfachheit und Organisation.

Weitere verwandte Artikel