Injection de dépendances (Dependency Injection) en Go : guide pratique

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : Pourquoi l'injection de dépendances est-elle importante en Go ?



Go (Golang) est un langage qui valorise la simplicité et l'explicite. Cependant, à mesure qu'un projet grandit, le besoin de gérer les dépendances entre les composants apparaît. Dependency Injection (DI) ou injection de dépendances est un pattern de conception qui aide à créer un code faiblement couplé, testable et facile à maintenir. Contrairement aux frameworks utilisant la magie de la réflexion (comme Spring en Java), Go propose une approche plus pragmatique : passer les dépendances via des interfaces et des constructeurs.



Dans cet article, nous allons voir comment implémenter la DI en Go sans bibliothèques tierces, en utilisant des interfaces pures, et nous examinerons également les conteneurs DI populaires. Vous apprendrez comment l'injection de dépendances aide à écrire des tests unitaires et à suivre les principes SOLID.



Les bases : Interfaces et injection par constructeur



En Go, il n'y a pas de classes, mais il y a des interfaces — ce sont des contrats de comportement. Ce sont elles qui sont la base de la DI. Au lieu de créer une implémentation concrète à l'intérieur d'une structure, nous la passons de l'extérieur.



Exemple sans DI (couplage fort)



package main

import "fmt"

// Logger — implémentation concrètetype Logger struct{}

func (l *Logger) Log(msg string) { fmt.Println("LOG:", msg)}

// Service — dépend fortement de Loggertype Service struct { logger *Logger}

func NewService() *Service { return &Service{logger: &Logger{}}}

func (s *Service) DoWork() { s.logger.Log("Travail effectué")}

func main() { svc := NewService() svc.DoWork()}


Problème : Service ne peut pas être testé sans créer un Logger réel. Il est impossible de le remplacer par un mock ou un autre logger.



Exemple avec DI via interface



package main

import "fmt"

// LogInterface — abstraction pour la journalisationtype LogInterface interface { Log(msg string)}

// ConsoleLogger — implémentationtype ConsoleLogger struct{}

func (l *ConsoleLogger) Log(msg string) { fmt.Println("LOG:", msg)}

// Service — dépend maintenant de l'interfacetype Service struct { logger LogInterface}

// NewService — constructeur avec injection de dépendancefunc NewService(logger LogInterface) *Service { return &Service{logger: logger}}

func (s *Service) DoWork() { s.logger.Log("Travail effectué")}

func main() { logger := &ConsoleLogger{} svc := NewService(logger) svc.DoWork()}


Maintenant, Service fonctionne avec n'importe quel objet implémentant LogInterface. Cela simplifie les tests : nous pouvons passer un mock (simulateur).



Technique avancée : Conteneurs DI en Go



Lorsque le projet s'agrandit, passer manuellement les dépendances devient fastidieux. Les conteneurs DI viennent à la rescousse — ce sont des bibliothèques qui résolvent automatiquement le graphe de dépendances. En Go, google/wire (de Google) et uber-go/dig (d'Uber) sont populaires.



Exemple avec google/wire



Wire utilise la génération de code, ce qui exclut la réflexion à l'exécution — cela correspond à l'esprit de Go.



// wire.go//go:build wireinject// +build wireinject

package main

import "github.com/google/wire"

func InitializeService() *Service { wire.Build(NewService, NewConsoleLogger) return nil}


Après avoir exécuté wire, il générera le code qui créera ConsoleLogger et le passera à NewService.



Exemple avec uber-go/dig



Dig fonctionne via la réflexion, mais est très pratique pour l'assemblage dynamique.



package main

import ( "go.uber.org/dig")

func main() { container := dig.New()

// Enregistrement des constructeurs container.Provide(NewConsoleLogger) container.Provide(NewService)

// Appel de la fonction qui utilise la dépendance

Blogs

Book Recommendations