Introdução: Por que o Dependency Injection é importante em Go?
Go (Golang) é uma linguagem que valoriza simplicidade e clareza. No entanto, à medida que o projeto cresce, surge a necessidade de gerenciar dependências entre componentes. Dependency Injection (DI) ou injeção de dependências é um padrão de design que ajuda a criar código fracamente acoplado, testável e facilmente mantível. Diferente de frameworks com magia de reflexão (como Spring em Java), Go oferece uma abordagem mais pragmática: passar dependências através de interfaces e construtores.
Neste artigo, vamos analisar como implementar DI em Go sem bibliotecas de terceiros, usando interfaces puras, e também considerar contêineres DI populares. Você aprenderá como a injeção de dependências ajuda a escrever testes unitários e seguir os princípios SOLID.
Fundamentos: Interfaces e injeção via construtor
Em Go não há classes, mas existem interfaces — que são contratos de comportamento. Elas são a base para DI. Em vez de criar uma implementação concreta dentro da struct, nós a passamos de fora.
Exemplo sem DI (acoplamento forte)
package main
import "fmt"
// Logger — implementação concretatype Logger struct{}
func (l *Logger) Log(msg string) { fmt.Println("LOG:", msg)}
// Service — depende fortemente de Loggertype Service struct { logger *Logger}
func NewService() *Service { return &Service{logger: &Logger{}}}
func (s *Service) DoWork() { s.logger.Log("Trabalho concluído")}
func main() { svc := NewService() svc.DoWork()}Problema: Service não pode ser testado sem criar um Logger real. Substituí-lo por um mock ou outro logger é impossível.
Exemplo com DI via interface
package main
import "fmt"
// LogInterface — abstração para loggingtype LogInterface interface { Log(msg string)}
// ConsoleLogger — implementaçãotype ConsoleLogger struct{}
func (l *ConsoleLogger) Log(msg string) { fmt.Println("LOG:", msg)}
// Service — agora depende da interfacetype Service struct { logger LogInterface}
// NewService — construtor com injeção de dependênciafunc NewService(logger LogInterface) *Service { return &Service{logger: logger}}
func (s *Service) DoWork() { s.logger.Log("Trabalho concluído")}
func main() { logger := &ConsoleLogger{} svc := NewService(logger) svc.DoWork()}Agora Service funciona com qualquer objeto que implemente LogInterface. Isso simplifica os testes: podemos passar um mock (stub).
Técnica avançada: Contêineres DI em Go
Quando o projeto cresce, passar dependências manualmente se torna tedioso. É aí que entram os contêineres DI — bibliotecas que resolvem automaticamente o grafo de dependências. Em Go, são populares google/wire (do Google) e uber-go/dig (da Uber).
Exemplo com google/wire
Wire usa code generation, o que elimina a reflexão em tempo de execução — isso está no espírito do Go.
// wire.go//go:build wireinject// +build wireinject
package main
import "github.com/google/wire"
func InitializeService() *Service { wire.Build(NewService, NewConsoleLogger) return nil}Após executar wire, ele gerará código que criará um ConsoleLogger e o passará para NewService.
Exemplo com uber-go/dig
Dig funciona através de reflexão, mas é muito conveniente para montagem dinâmica.
package main
import ( "go.uber.org/dig")
func main() { container := dig.New()
// Registramos os construtores container.Provide(NewConsoleLogger) container.Provide(NewService)
// Chamamos a função que usa a dependên