Injeção de Dependência em Go: Padrões e Melhores Práticas
Domine padrões de DI para código Go testável
A injeção de dependência (DI) é um padrão de projeto fundamental que promove código limpo, testável e mantível em aplicações Go.
Seja você construindo APIs REST, implementando padrões de banco de dados multi-tenant ou trabalhando com bibliotecas ORM, entender a injeção de dependência melhorará significativamente a qualidade do seu código.

O que é Injeção de Dependência?
A injeção de dependência é um padrão de projeto onde os componentes recebem suas dependências de fontes externas, em vez de criá-las internamente. Essa abordagem desacopla os componentes, tornando seu código mais modular, testável e fácil de manter.
Em Go, a injeção de dependência é particularmente poderosa devido à filosofia de design baseada em interfaces da linguagem. A satisfação implícita de interfaces em Go significa que você pode trocar implementações facilmente sem modificar o código existente.
Por que Usar Injeção de Dependência em Go?
Melhor Testabilidade: Ao injetar dependências, você pode facilmente substituir implementações reais por mocks ou duplicatas de teste. Isso permite que você escreva testes unitários que são rápidos, isolados e não exigem serviços externos como bancos de dados ou APIs.
Melhor Manutenibilidade: As dependências tornam-se explícitas no seu código. Ao olhar para uma função construtora, você vê imediatamente o que um componente requer. Isso torna a base de código mais fácil de entender e modificar.
Acoplamento Fraco: Os componentes dependem de abstrações (interfaces) em vez de implementações concretas. Isso significa que você pode alterar implementações sem afetar o código dependente.
Flexibilidade: Você pode configurar diferentes implementações para diferentes ambientes (desenvolvimento, teste, produção) sem alterar sua lógica de negócios.
Injeção via Construtor: A Maneira Go
A maneira mais comum e idiomática de implementar injeção de dependência em Go é através de funções construtoras. Elas são tipicamente nomeadas NewXxx e aceitam dependências como parâmetros.
Exemplo Básico
Aqui está um exemplo simples demonstrando a injeção via construtor:
// Define uma interface para o repositório
type UserRepository interface {
FindByID(id int) (*User, error)
Save(user *User) error
}
// O serviço depende da interface do repositório
type UserService struct {
repo UserRepository
}
// O construtor injeta a dependência
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}
// Os métodos usam a dependência injetada
func (s *UserService) GetUser(id int) (*User, error) {
return s.repo.FindByID(id)
}
Este padrão deixa claro que UserService requer um UserRepository. Você não pode criar um UserService sem fornecer um repositório, o que evita erros de tempo de execução devidos a dependências faltantes.
Múltiplas Dependências
Quando um componente tem múltiplas dependências, basta adicioná-las como parâmetros do construtor:
type EmailService interface {
Send(to, subject, body string) error
}
type Logger interface {
Info(msg string)
Error(msg string, err error)
}
type OrderService struct {
repo OrderRepository
emailSvc EmailService
logger Logger
paymentSvc PaymentService
}
func NewOrderService(
repo OrderRepository,
emailSvc EmailService,
logger Logger,
paymentSvc PaymentService,
) *OrderService {
return &OrderService{
repo: repo,
emailSvc: emailSvc,
logger: logger,
paymentSvc: paymentSvc,
}
}
Design de Interfaces para Injeção de Dependência
Um dos princípios-chave ao implementar injeção de dependência é o Princípio da Inversão de Dependência (DIP): módulos de alto nível não devem depender de módulos de baixo nível; ambos devem depender de abstrações.
Em Go, isso significa definir interfaces pequenas e focadas que representem apenas o que seu componente precisa:
// Bom: Interface pequena e focada
type PaymentProcessor interface {
ProcessPayment(amount float64) error
}
// Ruim: Interface grande com métodos desnecessários
type PaymentService interface {
ProcessPayment(amount float64) error
RefundPayment(id string) error
GetPaymentHistory(userID int) ([]Payment, error)
UpdatePaymentMethod(userID int, method PaymentMethod) error
// ... muitos mais métodos
}
A interface menor segue o Princípio da Segregação de Interface—os clientes não devem depender de métodos que não usam. Isso torna seu código mais flexível e mais fácil de testar.
Exemplo do Mundo Real: Abstração de Banco de Dados
Ao trabalhar com bancos de dados em aplicações Go, você frequentemente precisará abstrair operações de banco de dados. Veja como a injeção de dependência ajuda:
// Interface de banco de dados - abstração de alto nível
type DB interface {
Query(ctx context.Context, query string, args ...interface{}) (Rows, error)
Exec(ctx context.Context, query string, args ...interface{}) (Result, error)
BeginTx(ctx context.Context) (Tx, error)
}
// O repositório depende da abstração
type UserRepository struct {
db DB
}
func NewUserRepository(db DB) *UserRepository {
return &UserRepository{db: db}
}
func (r *UserRepository) FindByID(ctx context.Context, id int) (*User, error) {
query := "SELECT id, name, email FROM users WHERE id = $1"
rows, err := r.db.Query(ctx, query, id)
if err != nil {
return nil, err
}
defer rows.Close()
// ... parse rows
}
Este padrão é especialmente útil ao implementar padrões de banco de dados multi-tenant, onde você pode precisar alternar entre diferentes implementações de banco de dados ou estratégias de conexão.
O Padrão Composition Root
O Composition Root é o local onde você monta todas as suas dependências no ponto de entrada da aplicação (tipicamente main). Isso centraliza a configuração de dependências e torna o grafo de dependências explícito.
func main() {
// Inicializa dependências de infraestrutura
db := initDatabase()
logger := initLogger()
// Inicializa repositórios
userRepo := NewUserRepository(db)
orderRepo := NewOrderRepository(db)
// Inicializa serviços com dependências
emailSvc := NewEmailService(logger)
paymentSvc := NewPaymentService(logger)
userSvc := NewUserService(userRepo, logger)
orderSvc := NewOrderService(orderRepo, emailSvc, logger, paymentSvc)
// Inicializa manipuladores HTTP
userHandler := NewUserHandler(userSvc)
orderHandler := NewOrderHandler(orderSvc)
// Conecta as rotas
router := setupRouter(userHandler, orderHandler)
// Inicia o servidor
log.Fatal(http.ListenAndServe(":8080", router))
}
Esta abordagem deixa claro como sua aplicação está estruturada e de onde vêm as dependências. É particularmente valioso ao construir APIs REST em Go, onde você precisa coordenar múltiplas camadas de dependências.
Frameworks de Injeção de Dependência
Para aplicações maiores com grafos de dependência complexos, gerenciar dependências manualmente pode se tornar trabalhoso. Go possui vários frameworks de DI que podem ajudar:
Google Wire (DI em Tempo de Compilação)
Wire é uma ferramenta de injeção de dependência em tempo de compilação que gera código. É segura quanto aos tipos e não tem sobrecarga em tempo de execução.
Instalação:
go install github.com/google/wire/cmd/wire@latest
Exemplo:
// wire.go
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
func InitializeApp() (*App, error) {
wire.Build(
NewDB,
NewUserRepository,
NewUserService,
NewUserHandler,
NewApp,
)
return &App{}, nil
}
Wire gera o código de injeção de dependência em tempo de compilação, garantindo segurança de tipos e eliminando a sobrecarga de reflexão em tempo de execução.
Uber Dig (DI em Tempo de Execução)
Dig é um framework de injeção de dependência em tempo de execução que usa reflexão. É mais flexível, mas tem algum custo em tempo de execução.
Exemplo:
import "go.uber.org/dig"
func main() {
container := dig.New()
// Registra provedores
container.Provide(NewDB)
container.Provide(NewUserRepository)
container.Provide(NewUserService)
container.Provide(NewUserHandler)
// Invoca função que precisa de dependências
err := container.Invoke(func(handler *UserHandler) {
// Usa handler
})
if err != nil {
log.Fatal(err)
}
}
Quando Usar Frameworks
Use um framework quando:
- Seu grafo de dependência é complexo com muitos componentes interdependentes
- Você tem múltiplas implementações da mesma interface que precisam ser selecionadas com base na configuração
- Você deseja resolução automática de dependências
- Você está construindo uma aplicação grande onde o encaminhamento manual torna-se propenso a erros
Mantenha a DI manual quando:
- Sua aplicação é pequena ou de médio porte
- O grafo de dependência é simples e fácil de seguir
- Você quer manter as dependências mínimas e explícitas
- Você prefere código explícito em vez de código gerado
Testando com Injeção de Dependência
Um dos principais benefícios da injeção de dependência é a melhor testabilidade. Veja como a DI torna os testes mais fáceis:
Exemplo de Teste Unitário
// Implementação mock para testes
type mockUserRepository struct {
users map[int]*User
err error
}
func (m *mockUserRepository) FindByID(id int) (*User, error) {
if m.err != nil {
return nil, m.err
}
return m.users[id], nil
}
func (m *mockUserRepository) Save(user *User) error {
if m.err != nil {
return m.err
}
m.users[user.ID] = user
return nil
}
// Teste usando o mock
func TestUserService_GetUser(t *testing.T) {
mockRepo := &mockUserRepository{
users: map[int]*User{
1: {ID: 1, Name: "John", Email: "john@example.com"},
},
}
service := NewUserService(mockRepo)
user, err := service.GetUser(1)
assert.NoError(t, err)
assert.Equal(t, "John", user.Name)
}
Este teste é executado rapidamente, não requer um banco de dados e testa sua lógica de negócios de forma isolada. Ao trabalhar com bibliotecas ORM em Go, você pode injetar repositórios mock para testar a lógica do serviço sem configuração de banco de dados.
Padrões Comuns e Melhores Práticas
1. Use Segregação de Interfaces
Mantenha as interfaces pequenas e focadas no que o cliente realmente precisa:
// Bom: O cliente só precisa ler usuários
type UserReader interface {
FindByID(id int) (*User, error)
FindByEmail(email string) (*User, error)
}
// Interface separada para escrita
type UserWriter interface {
Save(user *User) error
Delete(id int) error
}
2. Retorne Erros dos Construtores
Os construtores devem validar dependências e retornar erros se a inicialização falhar:
func NewUserService(repo UserRepository) (*UserService, error) {
if repo == nil {
return nil, errors.New("user repository cannot be nil")
}
return &UserService{repo: repo}, nil
}
3. Use Contexto para Dependências com Escopo de Requisição
Para dependências específicas de requisição (como transações de banco de dados), passe-as via contexto:
type ctxKey string
const dbKey ctxKey = "db"
func WithDB(ctx context.Context, db DB) context.Context {
return context.WithValue(ctx, dbKey, db)
}
func DBFromContext(ctx context.Context) (DB, bool) {
db, ok := ctx.Value(dbKey).(DB)
return db, ok
}
4. Evite Injeção Excessiva
Não injete dependências que são verdadeiramente detalhes de implementação interna. Se um componente cria e gerencia seus próprios objetos auxiliares, isso está bem:
// Bom: Auxiliar interno não precisa de injeção
type UserService struct {
repo UserRepository
// Cache interno - não precisa de injeção
cache map[int]*User
}
func NewUserService(repo UserRepository) *UserService {
return &UserService{
repo: repo,
cache: make(map[int]*User),
}
}
5. Documente Dependências
Use comentários para documentar por que as dependências são necessárias e quaisquer restrições:
// UserService lida com lógica de negócios relacionada a usuários.
// Ele requer um UserRepository para acesso a dados e um Logger para
// rastreamento de erros. O repositório deve ser thread-safe se usado
// em contextos concorrentes.
func NewUserService(repo UserRepository, logger Logger) *UserService {
// ...
}
Quando NÃO Usar Injeção de Dependência
A injeção de dependência é uma ferramenta poderosa, mas nem sempre é necessária:
Pule a DI para:
- Objetos de valor simples ou estruturas de dados
- Funções auxiliares internas ou utilitários
- Scripts pontuais ou pequenos utilitários
- Quando a instanciação direta for mais clara e simples
Exemplo de quando NÃO usar DI:
// Estrutura simples - não há necessidade de DI
type Point struct {
X, Y float64
}
func NewPoint(x, y float64) Point {
return Point{X: x, Y: y}
}
// Utilitário simples - não há necessidade de DI
func FormatCurrency(amount float64) string {
return fmt.Sprintf("$%.2f", amount)
}
Integração com o Ecossistema Go
A injeção de dependência funciona perfeitamente com outros padrões e ferramentas do Go. Ao construir aplicações que usam a biblioteca padrão do Go para web scraping ou gerando relatórios em PDF, você pode injetar esses serviços em sua lógica de negócios:
type PDFGenerator interface {
GenerateReport(data ReportData) ([]byte, error)
}
type ReportService struct {
pdfGen PDFGenerator
repo ReportRepository
}
func NewReportService(pdfGen PDFGenerator, repo ReportRepository) *ReportService {
return &ReportService{
pdfGen: pdfGen,
repo: repo,
}
}
Isso permite que você troque implementações de geração de PDF ou use mocks durante os testes.
Conclusão
A injeção de dependência é uma pedra angular para escrever código Go mantível e testável. Ao seguir os padrões descritos neste artigo—injeção via construtor, design baseado em interfaces e o padrão Composition Root—você criará aplicações que são mais fáceis de entender, testar e modificar.
Comece com a injeção de construtor manual para aplicações pequenas e de médio porte, e considere frameworks como Wire ou Dig à medida que seu grafo de dependências cresce. Lembre-se de que o objetivo é clareza e testabilidade, não complexidade por si só. Se você estiver estruturando sua aplicação em torno de manipuladores de comandos e consultas, Implementando CQRS em Go mostra como a mesma abordagem de injeção via construtor conecta limpa e sem cerimônia as structs Application, Commands e Queries.
Para mais recursos de desenvolvimento Go, confira nosso Cheat Sheet de Go para referência rápida sobre sintaxe Go e padrões comuns.
Links Úteis
- Cheat Sheet de Go
- Alternativas ao Beautiful Soup para Go
- Gerando PDF em GO - Bibliotecas e exemplos
- Padrões de Banco de Dados Multi-Tenant com exemplos em Go
- ORM a usar em GO: GORM, sqlc, Ent ou Bun?
- Construindo APIs REST em Go: Guia Completo
Recursos Externos
- Como Usar Injeção de Dependência em Go - freeCodeCamp
- Melhores Práticas para Inversão de Dependência em Golang - Relia Software
- Guia Prático de Injeção de Dependência em Go - Relia Software
- Google Wire - Injeção de Dependência em Tempo de Compilação
- Uber Dig - Injeção de Dependência em Tempo de Execução
- Princípios SOLID em Go - Software Patterns Lexicon
- Hub de Arquitetura de Aplicação — Design de API, estrutura de código e padrões de integração