Lernen Sie kennen: Ihr Code isoliert von der Außenwelt
\n\nHaben 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?
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\nIn 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\nWas ist Hexagonale Architektur in einfachen Worten
\n\nStellen 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\nWenn 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\nDie grundlegenden Konzepte:
\n- \n
- Port – Ein Interface, das definiert, wie die Außenwelt mit dem Kern interagieren kann. Zum Beispiel
UserRepositorymit den MethodenSaveundFindByID. \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
Dieser Ansatz bietet drei Superkräfte: Testbarkeit (des Kerns ohne DB), Flexibilität (Austausch jedes Details) und klare Verantwortungsgrenzen.
\n\nWarum Go sich ideal für Hexagonal eignet
\n\nGo 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\nAuß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\nProjektstruktur: Wie man Ordner organisiert
\n\nEs gibt keinen einheitlichen Standard, aber hier ist ein bewährtes Schema, das in Produktionsprojekten verwendet wird:
\n\nmyapp/\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\nWichtig: Der Ordner internal verbietet Importe von außen – ideal zum Schutz des Kerns. Adapter liegen separat und können leicht ausgetauscht werden.
Code schreiben: Von Interfaces bis zur Implementierung
\n\nLassen Sie uns einen einfachen Dienst für die Benutzerverwaltung erstellen. Beginnen wir mit dem Kern.
\n\nSchritt 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)\