Injeção de Dependência em Go: da teoria ao código real
Injeção de Dependência (DI) é um dos padrões de design fundamentais que ajuda a criar código fracamente acoplado, testável e de fácil manutenção. Em Go, com sua filosofia de simplicidade e clareza, a injeção de dependências pode ser implementada tanto manualmente quanto com o uso de bibliotecas. Neste artigo, vamos explorar como aplicar DI em Golang na prática, abordando a abordagem manual e também a popular biblioteca google/wire.
1. Por que a Injeção de Dependência é necessária em Go?
Imagine que você está escrevendo um serviço HTTP para trabalhar com usuários. Sem DI, seu código pode se parecer com isto:
package main
import "database/sql"
type UserHandler struct { db *sql.DB}
func NewUserHandler() *UserHandler { db, _ := sql.Open("postgres", "conn_string") return &UserHandler{db: db}}
func (h *UserHandler) GetUser(id int) (*User, error) { // trabalhando com h.db}Os problemas são óbvios:
- Acoplamento rígido — o handler cria o banco de dados por conta própria.
- Dificuldade de teste — é impossível substituir
dbpor um mock. - Violação do Princípio da Responsabilidade Única — o handler é responsável tanto pela criação das dependências quanto pela lógica de negócios.
DI resolve esses problemas passando as dependências de fora:
type UserHandler struct { db Database}
func NewUserHandler(db Database) *UserHandler { return &UserHandler{db: db}}
// Agora é fácil substituir por um mocktype Database interface { GetUser(id int) (*User, error)}2. Injeção manual de dependências: simplicidade e controle
Go não requer frameworks para DI. A maneira mais simples e transparente é passar dependências através de construtores. Vamos considerar um exemplo com três camadas da aplicação: handler → service → repository.
// repository/user.gotype UserRepository interface { FindByID(id int) (*User, error)}
type postgresUserRepo struct { db *sql.DB}
func NewPostgresUserRepo(db *sql.DB) *postgresUserRepo { return &postgresUserRepo{db: db}}
func (r *postgresUserRepo) FindByID(id int) (*User, error) { // implementação}// service/user.gotype UserService struct { repo UserRepository}
func NewUserService(repo UserRepository) *UserService { return &UserService{repo: repo}}
func (s *UserService) GetUser(id int) (*User, error) { return s.repo.FindByID(id)}// handler/user.gotype UserHandler struct { svc *UserService}
func NewUserHandler(svc *UserService) *UserHandler { return &UserHandler{svc: svc}}
func (h *UserHandler) HandleGetUser(w http.ResponseWriter, r *http.Request) { // trabalhando com h.svc}Agora a montagem da aplicação ocorre em main.go:
func main() { db, _ := sql.Open("postgres", os.Getenv("DB_URL")) repo := NewPostgresUserRepo(db) svc := NewUserService(repo) handler := NewUserHandler(svc) http.HandleFunc("/user", handler.HandleGetUser) log.Fatal(http.ListenAndServe(":8080", nil))}Vantagens do DI manual:
- Transparência total — você vê todas as dependências explicitamente.
- Sem magia e dependências desnecessárias no projeto.
- Ideal para projetos pequenos e médios.
Desvantagens: à medida que a aplicação cresce (50+ componentes), a montagem manual se torna complicada e propensa a erros.
3. Automação com google/wire
A biblioteca github.com/google/wire gera código para montagem de dependências. Ela não é um container em tempo de execução — é um gerador de código, o que está totalmente alinhado com a filosofia do Go.
Instalação:
go get github.com/google/wir