Injection de Dépendances en Go : de la théorie au code réel
\n\nL'Injection de Dépendances (DI) est l'un des modèles de conception clés qui aide à créer un code faiblement couplé, testable et maintenable. En Go, avec sa philosophie de simplicité et de clarté, l'injection de dépendances peut être implémentée manuellement ou à l'aide de bibliothèques. Dans cet article, nous allons voir comment appliquer correctement la DI en Golang en pratique, examiner l'approche manuelle ainsi que la bibliothèque populaire google/wire.
1. Pourquoi l'Injection de Dépendances en Go est-elle nécessaire ?
\n\nImaginez que vous écrivez un service HTTP pour gérer les utilisateurs. Sans DI, votre code pourrait ressembler à ceci :
\n\npackage main\n\nimport "database/sql"\n\ntype UserHandler struct {\n db *sql.DB\n}\n\nfunc NewUserHandler() *UserHandler {\n db, _ := sql.Open("postgres", "conn_string")\n return &UserHandler{db: db}\n}\n\nfunc (h *UserHandler) GetUser(id int) (*User, error) {\n // travail avec h.db\n}\n\n\nLes problèmes sont évidents :
\n- \n
- Couplage fort — le handler crée lui-même la base de données. \n
- Difficulté de test — impossible de remplacer
dbpar un mock. \n - Violation du principe de Responsabilité Unique — le handler est responsable à la fois de la création des dépendances et de la logique métier. \n
La DI résout ces problèmes en transmettant les dépendances de l'extérieur :
\n\ntype UserHandler struct {\n db Database\n}\n\nfunc NewUserHandler(db Database) *UserHandler {\n return &UserHandler{db: db}\n}\n\n// Maintenant, on peut facilement remplacer par un mock\ntype Database interface {\n GetUser(id int) (*User, error)\n}\n\n\n2. Injection manuelle de dépendances : simplicité et contrôle
\n\nGo ne nécessite pas de frameworks pour la DI. La manière la plus simple et transparente est de passer les dépendances via les constructeurs. Prenons l'exemple d'une application à trois niveaux : handler → service → repository.
\n\n// repository/user.go\ntype UserRepository interface {\n FindByID(id int) (*User, error)\n}\n\ntype postgresUserRepo struct {\n db *sql.DB\n}\n\nfunc NewPostgresUserRepo(db *sql.DB) *postgresUserRepo {\n return &postgresUserRepo{db: db}\n}\n\nfunc (r *postgresUserRepo) FindByID(id int) (*User, error) {\n // implémentation\n}\n\n\n// service/user.go\ntype UserService struct {\n repo UserRepository\n}\n\nfunc NewUserService(repo UserRepository) *UserService {\n return &UserService{repo: repo}\n}\n\nfunc (s *UserService) GetUser(id int) (*User, error) {\n return s.repo.FindByID(id)\n}\n\n\n// handler/user.go\ntype UserHandler struct {\n svc *UserService\n}\n\nfunc NewUserHandler(svc *UserService) *UserHandler {\n return &UserHandler{svc: svc}\n}\n\nfunc (h *UserHandler) HandleGetUser(w http.ResponseWriter, r *http.Request) {\n // travail avec h.svc\n}\n\n\nMaintenant, l'assemblage de l'application se fait dans main.go :
func main() {\n db, _ := sql.Open("postgres", os.Getenv("DB_URL"))\n \n repo := NewPostgresUserRepo(db)\n svc := NewUserService(repo)\n handler := NewUserHandler(svc)\n \n http.HandleFunc("/user", handler.HandleGetUser)\n log.Fatal(http.ListenAndServe(":8080", nil))\n}\n\n\nAvantages de la DI manuelle :
\n- \n
- Transparence totale — vous voyez toutes les dépendances explicitement. \n
- Pas de magie ni de dépendances superflues dans le projet. \n
- Idéal pour les petits et moyens projets. \n
Inconvénients : lorsque l'application grandit (50+ composants), l'assemblage manuel devient lourd et sujet aux erreurs.
\n\n3. Automatisation avec google/wire
\n\nLa bibliothèque github.com/google/wire génère du code pour l'assemblage des dépendances. Ce n'est pas un conteneur d'exécution — c'est un générateur de code, ce qui correspond parfaitement à la philosophie de Go.
Installation :
\ngo get github.com/google/wir