Zurück zur Übersicht

Wissen, 25. Juni 2024

Go-Projektstruktur und Package-Layout

Go macht keinerlei Vorgaben über Projektstruktur und Package-Layout. Über die Zeit haben sich eine Reihe von Best-Practices etabliert, die in diesem Beitrag beschrieben werden.

Projektstruktur

Auf oberster Ebene enthalten Go-Projekte eine Reihe von Verzeichnissen für die unterschiedlichen Projekt-Artefakte. Beispiel:

cmd
  > api
      main.go
  > web
      main.go
config
      config.json
pkg
  > mysql
      repository.go
  > http
  > record
  channel.go   
  repository.go         
scripts
  sample.sh

Das Verzeichnis cmd enthält für jedes Target-Binary ein Unterverzeichnis mit je einem Main-Programm. Das Verzeichnis config enthält Konfigurationsdateien, pkg die eigentliche Anwendung und scripts eventuell benötigte Skripte. Die Top-Level-Directories werden je nach Projekterfordernissen erweitert oder reduziert (mehr dazu in [2]). Neben den Artefakt-Verzeichnissen unterscheidet das Modell drei Package-Arten: Root-, Sub- und Executable-Packages.

Root-Package: Domain Model

Das Root-Package pkg enthält das Domain Model der Anwendung, sowie Sub-Package-übergreifende Funktionen. Das Root-Package referenziert weder Sub-Packages noch externe Abhängigkeiten, wie z.B. Datenbank- oder HTTP-Code.

Sub-Packages: Usecase oder Adapter

Sub-Packages unterhalb von pkg enthalten Usecases oder Adapter. Im Beispiel ist das Sub-Package record ein Usecase-Package, das den Usecase „Aufzeichnen von Tests“ implementiert. Usecase-Packages nutzen Artefakte des Root-Packages und sind frei von externen Abhängigkeiten. Adapter-Packages enthalten Typen und Funktionen, die die Anwendung mit ihrem Kontext verbinden. Im Beispiel enthält das Sub-Package mysql Code für den Zugriff auf MySQL-Datenbanken.

Executable-Packages: Main

Executable-Packages enthalten eine Main-Datei pro Binärartefakt. Im Beispiel implementiert das Main-Package im Verzeichnis cmd/api eine HTTP-API und nutzt dafür Funktionen aus den Adapter-Sub-Packages mysql und http:

func main() {
    r := mux.NewRouter()
    repo := mysql.Repository{}
    r.HandleFunc("/record", http.StartRecording(repo))
    http.ListenAndServe(r)
}

Fazit

Das vorgeschlagene Package-Layout vereint Konzepte aus dem DDD und aus Hexagonalen Architekturen. Zyklische Abhängigkeiten werden vermieden. Das Domain-Modell ist frei von externen Abhängigkeiten und lässt sich einfach testen. Die Package-Struktur ist minimal verschachtelt. Package-Namen kommunizieren klar, was darin enthalten ist.

Referenzen

Autor:

Ralf Wirdemann
Software-Entwickler
  • Schwerpunkt Go und Java

  • Software-Architektur und -Modernisierung

  • Autor und Vortragender

Auch spannend