Injection de dépendances en Go : principes, exemples et meilleures pratiques
L'injection de dépendances (Dependency Injection, DI) est l'un des principes de conception clés qui permet de créer des applications flexibles, testables et faciles à maintenir. En langage Go, malgré sa simplicité et l'absence de conteneurs DI intégrés, l'injection de dépendances est largement utilisée. Dans cet article, nous allons voir ce qu'est l'injection de dépendances en Go, comment l'implémenter manuellement et à l'aide de bibliothèques populaires, ainsi que les meilleures pratiques.
Qu'est-ce que l'injection de dépendances ?
L'injection de dépendances est un modèle de conception dans lequel un objet reçoit ses dépendances de l'extérieur, plutôt que de les créer lui-même. L'objectif principal est de réduire le couplage entre les composants et d'améliorer la testabilité du code.
En Go, la DI est implémentée en passant des dépendances aux structures (structs) ou aux fonctions. Prenons un exemple simple sans DI :
package main
import "fmt"
type Logger struct{}
func (l *Logger) Log(message string) { fmt.Println("Log:", message)}
type Service struct { logger *Logger}
func NewService() *Service { return &Service{ logger: &Logger{}, // Couplage fort }}
func (s *Service) DoSomething() { s.logger.Log("Doing something")}Et maintenant le même exemple avec l'utilisation de la DI :
package main
import "fmt"
type Logger interface { Log(message string)}
type ConsoleLogger struct{}
func (l *ConsoleLogger) Log(message string) { fmt.Println("Log:", message)}
type Service struct { logger Logger // Dépendance via une interface}
func NewService(logger Logger) *Service { return &Service{logger: logger} // Injection de dépendance}
func (s *Service) DoSomething() { s.logger.Log("Doing something")}Avantages de l'injection de dépendances en Go
- Testabilité : Vous pouvez facilement remplacer les dépendances réelles par des mocks ou des stubs dans les tests.
- Flexibilité : Modifier le comportement de l'application se résume à remplacer l'implémentation d'une dépendance.
- Propreté du code : Le code devient plus modulaire, chaque structure est responsable uniquement de sa propre logique.
- Simplification du refactoring : Les dépendances sont explicitement déclarées, ce qui facilite la compréhension et la modification du code.
Méthodes d'injection de dépendances en Go
En Go, il existe plusieurs approches pour implémenter la DI. Examinons les principales.
1. Injection par constructeur (Constructor Injection)
La méthode la plus courante. Les dépendances sont passées via une fonction constructeur (NewSomething()).
type UserRepository interface { FindByID(id int) (*User, error)}
type UserService struct { repo UserRepository}
func NewUserService(repo UserRepository) *UserService { return &UserService{repo: repo}}2. Injection par champs de structure (Field Injection)
Les dépendances sont assignées directement aux champs de la structure. Cette approche est moins préférable car elle peut conduire à une initialisation incomplète de l'objet.
type UserService struct { Repo UserRepository}
func main() { svc := &UserService{} svc.Repo = &PostgresRepository{}}3. Injection par méthodes (Method Injection)
La dépendance est passée à la méthode, et non au constructeur. Utile pour les dépendances temporaires.
type UserService struct{}
func (s *UserService) GetUser(repo UserRepository, id int) (*User, error) { return repo.FindByID(id)}Exemple : construction d'un serveur HTTP avec DI
Prenons un exemple plus complexe — la création d'un serveur HTTP avec injection de dépendances.
package main
import ( "encoding/json" "net/http")
// Définissons les interfacestype UserRepository int