Dependency Injection in Go: Ein praktischer Leitfaden

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

Dependency Injection in Go: Von der Theorie zum echten Code



Dependency Injection (DI) ist eines der wichtigsten Entwurfsmuster, das hilft, lose gekoppelten, testbaren und wartbaren Code zu erstellen. In Go, mit seiner Philosophie der Einfachheit und Klarheit, kann die Abhängigkeitsinjektion sowohl manuell als auch mit Bibliotheken implementiert werden. In diesem Artikel werden wir uns ansehen, wie man DI in Golang in der Praxis richtig anwendet, den manuellen Ansatz sowie die beliebte Bibliothek google/wire behandeln.



1. Warum wird Dependency Injection in Go benötigt?



Stellen Sie sich vor, Sie schreiben einen HTTP-Dienst für die Benutzerverwaltung. Ohne DI könnte Ihr Code so aussehen:



package main

import "database/sql"

type UserHandler struct { db *sql.DB}

func NewUserHandler() *UserHandler { db, _ := sql.Open("postgres", "conn_string") return &UserHandler{db: db}}

func (h *UserHandler) GetUser(id int) (*User, error) { // Arbeit mit h.db}


Die Probleme sind offensichtlich:

  • Feste Kopplung — der Handler erstellt die Datenbank selbst.
  • Schwierigkeit beim Testen — es ist unmöglich, db durch einen Mock zu ersetzen.
  • Verletzung der Single Responsibility — der Handler ist sowohl für die Erstellung von Abhängigkeiten als auch für die Geschäftslogik verantwortlich.


DI löst diese Probleme, indem es Abhängigkeiten von außen übergibt:



type UserHandler struct {    db Database}

func NewUserHandler(db Database) *UserHandler { return &UserHandler{db: db}}

// Jetzt kann man leicht durch einen Mock ersetzentype Database interface { GetUser(id int) (*User, error)}


2. Manuelle Abhängigkeitsinjektion: Einfachheit und Kontrolle



Go benötigt keine Frameworks für DI. Der einfachste und transparenteste Weg ist, Abhängigkeiten über Konstruktoren zu übergeben. Betrachten wir ein Beispiel mit drei Anwendungsebenen: handler → service → repository.



// repository/user.gotype UserRepository interface {    FindByID(id int) (*User, error)}

type postgresUserRepo struct { db *sql.DB}

func NewPostgresUserRepo(db *sql.DB) *postgresUserRepo { return &postgresUserRepo{db: db}}

func (r *postgresUserRepo) FindByID(id int) (*User, error) { // Implementierung}


// service/user.gotype UserService struct {    repo UserRepository}

func NewUserService(repo UserRepository) *UserService { return &UserService{repo: repo}}

func (s *UserService) GetUser(id int) (*User, error) { return s.repo.FindByID(id)}


// handler/user.gotype UserHandler struct {    svc *UserService}

func NewUserHandler(svc *UserService) *UserHandler { return &UserHandler{svc: svc}}

func (h *UserHandler) HandleGetUser(w http.ResponseWriter, r *http.Request) { // Arbeit mit h.svc}


Jetzt erfolgt der Zusammenbau der Anwendung in main.go:



func main() {    db, _ := sql.Open("postgres", os.Getenv("DB_URL"))        repo := NewPostgresUserRepo(db)    svc := NewUserService(repo)    handler := NewUserHandler(svc)        http.HandleFunc("/user", handler.HandleGetUser)    log.Fatal(http.ListenAndServe(":8080", nil))}


Vorteile der manuellen DI:

  • Volle Transparenz — Sie sehen alle Abhängigkeiten explizit.
  • Keine Magie und keine überflüssigen Abhängigkeiten im Projekt.
  • Ideal für kleine und mittlere Projekte.


Nachteile: Bei wachsender Anwendung (50+ Komponenten) wird der manuelle Zusammenbau umständlich und fehleranfällig.



3. Automatisierung mit google/wire



Die Bibliothek github.com/google/wire generiert Code für den Zusammenbau von Abhängigkeiten. Sie ist kein Laufzeit-Container – es ist ein Codegenerator, was vollständig der Go-Philosophie entspricht.



Installation:

go get github.com/google/wire                

Blogs

Buchempfehlungen