Injeção de Dependência em Go: para iniciantes
Injeção de Dependência (DI), ou injeção de dependências, é um dos principais padrões de design que ajuda a escrever código limpo, testável e sustentável. No mundo Go, onde simplicidade e clareza são altamente valorizadas, a DI permite evitar a criação de "objetos divinos" e acoplamentos rígidos entre componentes. Neste artigo, vamos explorar o que é Injeção de Dependência em Go, por que ela é necessária e como aplicá-la na prática, usando exemplos simples e fáceis de entender.
O que é Injeção de Dependência?
Imagine que você está escrevendo um programa que envia e-mails de boas-vindas aos usuários. Seu código provavelmente se pareceria com isto:
package main
import "fmt"
type EmailService struct{}
func (e *EmailService) SendWelcomeEmail(userEmail string) { fmt.Printf("Enviando e-mail para %s", userEmail)}
type UserController struct { emailService *EmailService}
func NewUserController() *UserController { return &UserController{ emailService: &EmailService{}, // Acoplamento rígido }}
func (uc *UserController) RegisterUser(email string) { // ... lógica de registro uc.emailService.SendWelcomeEmail(email)}
func main() { controller := NewUserController() controller.RegisterUser("user@example.com")}O problema deste código é que UserController cria ele mesmo a instância de EmailService. Isso é chamado de acoplamento rígido. Se você quiser substituir EmailService por, digamos, SmsService ou usar um objeto mock para testes, terá que alterar o código dentro de UserController.
Injeção de Dependência resolve esse problema. Em vez de criar dependências dentro da estrutura, nós as "injetamos" de fora. A ideia principal é: "Não pergunte o que você precisa. Diga o que você precisa e receba isso pronto".
Injeção manual de dependências através do construtor
A maneira mais simples e comum de DI em Go é passar dependências através de uma função construtora. Vamos reescrever nosso exemplo:
package main
import "fmt"
// Definimos uma interface para flexibilidadetype Notifier interface { Send(to string, message string) error}
type EmailService struct{}
func (e *EmailService) Send(to string, message string) error { fmt.Printf("Email para %s: %s", to, message) return nil}
type SmsService struct{}
func (s *SmsService) Send(to string, message string) error { fmt.Printf("SMS para %s: %s", to, message) return nil}
type UserController struct { notifier Notifier // Dependência através de interface}
// NewUserController — construtor que aceita a dependênciafunc NewUserController(n Notifier) *UserController { return &UserController{ notifier: n, }}
func (uc *UserController) RegisterUser(email string) { // ... lógica de negócio uc.notifier.Send(email, "Bem-vindo!")}
func main() { // Injetamos a dependência manualmente emailNotifier := &EmailService{} controller := NewUserController(emailNotifier) controller.RegisterUser("user@example.com")
// Facilmente trocamos a implementação smsNotifier := &SmsService{} controller2 := NewUserController(smsNotifier) controller2.RegisterUser("+123456789")}O que mudou?
- Definimos a interface
Notifier. AgoraUserControllernão sabe qual serviço está sendo usado — Email ou SMS. - O construtor
NewUserControlleraceitaNotifiercomo argumento. Isso é injeção de dependência. - Em
main, nós mesmos decidimos qual implementação passar.
Essa abordagem torna o código flexível, testável e alinhado ao princípio Inversão de Controle.