Einleitung: Warum Architektur kein Luxus, sondern eine Notwendigkeit ist
\n\nWenn Sie gerade erst anfangen, in Go zu schreiben, scheint alles einfach: Goroutinen fliegen, Kanäle summen und der Compiler erfreut mit Geschwindigkeit. Aber nach ein oder zwei Monaten verwandelt sich Ihr Pet-Projekt in ein Monster mit einem package main mit 3000 Zeilen und Gebeten „Hauptsache, es stürzt nicht ab“. Kommt Ihnen bekannt vor?
Go bietet leistungsstarke Werkzeuge, erzwingt aber keine starren Rahmen. Das ist sowohl ein Vor- als auch ein Nachteil. Ohne eine klare Architektur verlieren Sie sich schnell in Ihren eigenen Goroutinen. Genau hier helfen Architekturmuster in Go – bewährte Lösungen für typische Entwurfsprobleme.
\n\nIn diesem Artikel analysieren wir vier Schlüsselmuster, die das Leben wirklich einfacher machen: Repository, Service Layer, Worker Pool und Pipeline. Sie sehen Codebeispiele, verstehen, wann welches Muster anzuwenden ist, und können sie sofort in Ihren Projekten einsetzen. Los geht's!
\n\n1. Repository Pattern: Geschäftslogik von der Datenbank trennen
\n\nDas erste Muster, das jeder Go-Entwickler beherrschen sollte, ist das Repository. Sein Kern ist einfach: Wir erstellen eine Zwischenschicht zwischen der Geschäftslogik und dem Datenspeicher (DB, Dateien, externe APIs).
\n\nWarum wird das benötigt?
\n- \n
- Testbarkeit: Die echte Datenbank kann leicht durch einen Mock ersetzt werden. \n
- Flexibilität: Umzug von PostgreSQL zu MongoDB? Nur das Repository wird geändert. \n
- Sauberer Code: Der Handler weiß nicht, woher die Daten kommen – das ist die Aufgabe des Repositorys. \n
Implementierungsbeispiel
\nAngenommen, wir haben ein Benutzermodell:
\n\ntype User struct {\n ID int64\n Name string\n Email string\n}\n\nWir definieren das Repository-Interface:
\n\ntype UserRepository interface {\n GetByID(ctx context.Context, id int64) (*User, error)\n Create(ctx context.Context, user *User) error\n Update(ctx context.Context, user *User) error\n Delete(ctx context.Context, id int64) error\n}\n\nUnd seine Implementierung für PostgreSQL:
\n\ntype PostgresUserRepo struct {\n db *sql.DB\n}\n\nfunc (r *PostgresUserRepo) GetByID(ctx context.Context, id int64) (*User, error) {\n user := &User{}\n err := r.db.QueryRowContext(ctx, "SELECT id, name, email FROM users WHERE id = $1", id).\n Scan(&user.ID, &user.Name, &user.Email)\n if err != nil {\n return nil, fmt.Errorf("get user by id: %w", err)\n }\n return user, nil\n}\n\nJetzt arbeitet Ihr Handler oder Service nur mit dem Interface UserRepository, nicht mit einer bestimmten Datenbank. Das ist das Architekturmuster Repository in Go in Aktion.
2. Service Layer: Geschäftslogik unter Kontrolle
\n\nWenn das Repository für die Daten zuständig ist, dann der Service Layer für die Geschäftsregeln. Dieses Muster fasst verstreute Operationen zu sinnvollen Aktionen zusammen.
\n\nProblem ohne Service Layer
\nIch sehe oft Code, in dem der Handler alles macht: die Anfrage validieren, Berechtigungen prüfen, in die DB schreiben und loggen. Das verstößt gegen das Prinzip der einzigen Verantwortung (SRP).
\n\nWie behebt man das?
\nWir führen eine Serviceschicht ein:
\n\ntype UserService interface {\n Register(ctx context.Context, name, email string) (*User, error)\n GetProfile(ctx context.Context, userID int64) (*User, error)\n}\n\ntype userService struct {\n repo UserRepository\n mail MailSender\n}\n\nfunc (s *userService) Register(ctx context.Context, name, email string) (*User, error) {\n // 1. Validierung\n if name == "" || email == "" {\n return nil, ErrInvalidInput\n }\n\n // 2. Geschäftslogik: prüfen, ob die E-Mail bereits vergeben ist\n existing, _ := s.repo.GetByEmail(ctx, email)\n if existing != nil {\n return nil, ErrEmailAlreadyExists\n }\n\n // 3. Benutzer erstellen\n user := &User{Name: name, Email: e