Go und Hexagonale Architektur: Code schreiben, den man nicht zu ändern fürchtet

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Lernen Sie kennen: Ihr Code isoliert von der Außenwelt

\n\n

Haben Sie jemals Geschäftslogik umgeschrieben, nur weil Sie die Datenbank von PostgreSQL auf MongoDB gewechselt haben? Oder sind Sie kaltschweißig aufgewacht beim Gedanken, einen neuen HTTP-Endpunkt hinzufügen zu müssen, während die gesamte Anwendung eine einzige riesige main.go mit 3000 Zeilen ist?

\n\n

Wenn ja – willkommen im Club. Wenn nein – entweder schreiben Sie erst seit kurzem in Go oder Sie verwenden bereits die Hexagonale Architektur (auch bekannt als Ports & Adapters).

\n\n

In diesem Artikel werden wir durchgehen, wie man eine Go-Anwendung baut, die keine Angst vor Veränderungen hat. Weder ein Datenbankwechsel, noch der Umstieg von REST auf gRPC, noch ein neues Framework zwingen Sie, den Kern des Systems umzuschreiben. Nur Code. Nur Go. Ohne überflüssige Worte.

\n\n

Was ist Hexagonale Architektur in einfachen Worten

\n\n

Stellen Sie sich vor, Ihre Anwendung ist ein Raumschiff. Im Zentrum befindet sich die Kommandozentrale (Geschäftslogik). Rundherum gibt es Andockports. Über die Ports schließen Sie verschiedene Module an: Triebwerke (Datenbank), Sensoren (HTTP-Anfragen), Kommunikation mit der Erde (Nachrichtenwarteschlangen).

\n\n

Wenn Sie sich entscheiden, die Triebwerke auszutauschen – docken Sie einfach die alten ab und docken neue an. Die Kommandozentrale bemerkt den Austausch nicht einmal. Das ist die Hexagonale Architektur.

\n\n

Die grundlegenden Konzepte:

\n
    \n
  • Port – Ein Interface, das definiert, wie die Außenwelt mit dem Kern interagieren kann. Zum Beispiel UserRepository mit den Methoden Save und FindByID.
  • \n
  • Adapter – Eine konkrete Implementierung eines Ports. Ein Port kann viele Adapter haben: PostgreSQLAdapter, InMemoryAdapter, MockAdapter für Tests.
  • \n
  • Core (Kern) – Die Geschäftslogik, die nichts über die Außenwelt weiß. Sie arbeitet nur über Ports.
  • \n
\n\n

Dieser Ansatz bietet drei Superkräfte: Testbarkeit (des Kerns ohne DB), Flexibilität (Austausch jedes Details) und klare Verantwortungsgrenzen.

\n\n

Warum Go sich ideal für Hexagonal eignet

\n\n

Go erzwingt keine OOP, bietet aber Interfaces – das Einzige, was für Ports & Adapters benötigt wird. Interfaces in Go sind implizit: Eine Struktur erfüllt automatisch ein Interface, wenn sie dessen Methoden implementiert. Das bedeutet, Sie können einen Adapter austauschen, ohne eine einzige Zeile im Kern zu ändern.

\n\n

Außerdem wird Go zu einer Binärdatei kompiliert, was die Architektur besonders nützlich für Microservices macht. Und die integrierten Tests und Benchmarks ermöglichen es, die Isolation der Komponenten sofort zu überprüfen.

\n\n

Projektstruktur: Wie man Ordner organisiert

\n\n

Es gibt keinen einheitlichen Standard, aber hier ist ein bewährtes Schema, das in Produktionsprojekten verwendet wird:

\n\n
myapp/\n├── cmd/\n│   └── app/\n│       └── main.go          # Einstiegspunkt, Dependency Injection\n├── internal/\n│   ├── core/\n│   │   ├── domain/          # Entitäten, Value Objects\n│   │   ├── ports/           # Interfaces (Ports)\n│   │   └── services/        # Geschäftslogik (Use Cases)\n│   └── adapters/\n│       ├── api/             # HTTP/gRPC Adapter\n│       ├── db/              # Repository-Implementierungen\n│       └── mq/              # Nachrichtenwarteschlangen\n└── go.mod\n
\n\n

Wichtig: Der Ordner internal verbietet Importe von außen – ideal zum Schutz des Kerns. Adapter liegen separat und können leicht ausgetauscht werden.

\n\n

Code schreiben: Von Interfaces bis zur Implementierung

\n\n

Lassen Sie uns einen einfachen Dienst für die Benutzerverwaltung erstellen. Beginnen wir mit dem Kern.

\n\n

Schritt 1. Domain und Ports

\n\n
// internal/core/domain/user.go\npackage domain\n\nimport "time"\n\ntype User struct {\n    ID        string\n    Email     string\n    Name      string\n    CreatedAt time.Time\n}\n\n// internal/core/ports/repositories.go\npackage ports\n\nimport "myapp/internal/core/domain"\n\ntype UserRepository interface {\n    Save(user domain.User) error\n    FindByID(id string) (domain.User, error)\n    FindByEmail(email string) (domain.User, error)\                

Blogs

Buchempfehlungen