DDD und Clean Architecture in Go: Ein pragmatischer Ansatz ohne Überengineering
Technische Schulden durch architekturelle Probleme verzehren bis zu 40 % der IT-Budgets von Unternehmen. In Go-Projekten äußert sich das oft in überkomplizierten Strukturen: 200 Dateien voller leerer Interfaces und Schichten, in denen die Geschäftslogik verschüttet geht. Die Ursache ist die Fehlanwendung von DDD- und Clean-Architecture-Mustern, die aus Java oder C# übernommen wurden. Pragmatisch eingesetzt, lösen diese Muster jedoch Komplexitätsprobleme. So vermeiden Sie Überengineering und bauen ein wartbares System auf.
Warum werden Go-Projekte zu einem Labyrinth aus Dateien?
Entwickler, die aus anderen Sprachen zu Go kommen, schleppen oft architektonische Gewohnheiten mit, die nicht zum idiomatischen Go passen. Laut Go Developer Survey 2024 ist das größte Problem die Aufrechterhaltung einheitlicher Code-Standards aufgrund unterschiedlicher Erfahrungsstufen und nicht-idiomatischer Muster. Zum Beispiel Folderstrukturen wie domain, application und infrastructure, die Java nachahmen, ohne die Kernprinzipien zu verstehen.
Forschung der Carnegie Mellon bestätigt: Architekturprobleme sind die Hauptquelle technischer Schulden. In Go sieht das so aus:
- Ein
dto-Ordner mit duplizierten Structs. mapperszwischen Schichten für triviale Umwandlungen.- Interfaces in
domain, die von Infrastructure abhängen (z. B.database/sql). - Use Cases, die nur Repositories aufrufen, ohne Geschäftslogik.
Das Ergebnis: 200 Dateien, aber keine beschreibt echte Geschäftsregeln. Clean Architecture und DDD werden nicht zur Lösung, sondern zum Problem selbst.
DDD: Nicht um Ordner, sondern um Sprache und Grenzen
Domain-Driven Design geht nicht um eine Reihe von Ordnern – es ist ein Ansatz zur Modellierung des Problembereichs. Wichtige Konzepte:
Ubiquitous Language
Entwickler und Geschäftspartner müssen überall dieselben Begriffe verwenden: im Code, in der Dokumentation und in Gesprächen. Wenn ein Manager "Bestellung bestätigen" sagt, sollte der Code eine Confirm()-Methode haben, nicht SetStatus(). Das beseitigt Missverständnisse und beschleunigt das Einarbeiten.
Bounded Context
Ein großes System besteht aus mehreren Kontexten. Zum Beispiel ist "Kunde" im Vertrieb (ein Lead mit Trichter) und im Support (Tickets) unterschiedlich modelliert. Bounded Context definiert klare Grenzen, in denen das Modell konsistent bleibt. In Microservices entspricht typischerweise ein Service einem Kontext.
Entity und Value Object
- Entity hat eine eindeutige ID und behält ihre Identität (z. B.
Order). Sie enthält Geschäftsregeln. - Value Object ist unveränderlich und durch seine Attribute definiert (z. B.
Money). Zwei Objekte mit identischen Feldern sind äquivalent.
Beispiel Value Object in Go:
type Money struct {
Amount int64
Currency string
}
func (m Money) Add(other Money) (Money, error) {
if m.Currency != other.Currency {
return Money{}, errors.New("currency mismatch")
}
return Money{Amount: m.Amount + other.Amount, Currency: m.Currency}, nil
}
Aggregate
Eine Gruppe von Objekten, die als Einheit behandelt wird. Es hat eine Wurzel (Aggregate Root), über die aller Zugriff erfolgt. Zum Beispiel umfasst Order (Wurzel) OrderItem. Regel: Änderungen nur über die Wurzel, um Konsistenz zu gewährleisten.
Clean Architecture: Die Dependency Rule als Grundlage
Das Wesen von Clean Architecture ist, dass Geschäftslogik nicht von Implementierungsdetails (Datenbanken, Frameworks) abhängen sollte. Die eine Regel: Abhängigkeiten zeigen nach innen (Dependency Rule).
So funktioniert es in Go
- domain: Enthält das Geschäftsmodell (Entity, Value Object, Repository-Interfaces). Keine externen Imports außer stdlib.
- application: Use Cases, die die Domäne orchestrieren. Hängt nur von domain ab.
- infrastructure: Adapter-Implementierungen (Datenbanken, HTTP-Clients). Hängt über Interfaces von domain ab.
- delivery: Einstiegspunkte (HTTP, gRPC). Hängt von application ab.
Wenn domain database/sql importiert, ist die Architektur kaputt. Korrektes Beispiel: Repository-Interface in domain deklariert, Implementierung in infrastructure/postgres.
Vorteile der Kombination aus DDD und Clean Architecture
| Aspekt | Effekt | Verbesserungsmetrik |
|---------------------|-------------------------------------|---------------------|
| Testbarkeit | Isolierung der Domäne von Infrastructure | +40% Abdeckung |
| Flexibilität | Adapter in Stunden austauschbar | -90% Zeit |
| Verständlichkeit | Klare Komponentengrenzen | -70% Einarbeitung |
Reale Projektstruktur: 200 Dateien vermeiden
Optimale Struktur für einen Go-Service:
internal/
domain/
order.go # Aggregate Order
order_repo.go # Repository interface
application/
create_order.go # Use case
infrastructure/
postgres/
order_repo.go # Implementation
delivery/
http/
order_handler.go
Wichtige Prinzipien:
- Repository-Interfaces in
domain, nichtinfrastructure. - Keine
dto- odermappers-Ordner, es sei denn, wirklich nötig: Wiederverwenden Sie dieselben Structs über Schichten hinweg, wenn Grenzen nicht verletzt werden. - Geschäftslogik nur in
domain. Zum Beispiel prüft dieCancel()-Methode fürOrderden Status:
func (o *Order) Cancel() error {
if o.status == StatusShipped {
return errors.New("cannot cancel shipped order")
}
o.status = StatusCancelled
return nil
}
Wichtige Erkenntnisse
- Ubiquitous Language ist die Grundlage von DDD. Wenn Code-Begriffe nicht zur Geschäftssprache passen, ist das Modell falsch.
- Dependency Rule ist der Kern von Clean Architecture. Verstöße führen zu Spaghetti-Code.
- Pragmatismus – schaffen Sie keine Schichten ohne Bedarf. In Go können Sie oft
dtoüberspringen, wenn Structs passen. - Aggregates definieren Transaktionsgrenzen. Ändern Sie nur über die Wurzel für Konsistenz.
- Testing – Domänen-Tests laufen ohne Infrastructure und beschleunigen die Entwicklung.
— Editorial Team
Noch keine Kommentare.