Injeção de Dependência em Go: princípios, exemplos e melhores práticas
Injeção de Dependência (DI) é um dos princípios-chave de design que ajuda a criar aplicações flexíveis, testáveis e fáceis de manter. Na linguagem Go, apesar de sua simplicidade e ausência de contêineres DI embutidos, a injeção de dependências é amplamente utilizada. Neste artigo, vamos analisar o que é Injeção de Dependência em Go, como implementá-la manualmente e com bibliotecas populares, além de considerar as melhores práticas.
O que é Injeção de Dependência?
Injeção de Dependência é um padrão de design no qual um objeto recebe suas dependências externamente, em vez de criá-las por conta própria. O objetivo principal é reduzir o acoplamento entre componentes e aumentar a testabilidade do código.
Em Go, a DI é implementada através da passagem de dependências para estruturas (structs) ou funções. Vamos considerar um exemplo simples sem 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{}, // Acoplamento rígido }}
func (s *Service) DoSomething() { s.logger.Log("Doing something")}E agora o mesmo exemplo usando 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 // Dependência através de interface}
func NewService(logger Logger) *Service { return &Service{logger: logger} // Injeção de dependência}
func (s *Service) DoSomething() { s.logger.Log("Doing something")}Vantagens da Injeção de Dependência em Go
- Testabilidade: Você pode facilmente substituir dependências reais por mocks ou stubs nos testes.
- Flexibilidade: Alterar o comportamento da aplicação se resume a substituir a implementação da dependência.
- Limpeza do código: O código se torna mais modular, cada estrutura é responsável apenas por sua lógica.
- Simplificação de refatoração: As dependências são explicitamente declaradas, facilitando a compreensão e alteração do código.
Formas de injeção de dependências em Go
Em Go, existem várias abordagens para implementar DI. Vamos considerar as principais.
1. Injeção via construtor (Constructor Injection)
A forma mais comum. As dependências são passadas através de uma função construtora (NewSomething()).
type UserRepository interface { FindByID(id int) (*User, error)}
type UserService struct { repo UserRepository}
func NewUserService(repo UserRepository) *UserService { return &UserService{repo: repo}}2. Injeção via campos da estrutura (Field Injection)
As dependências são atribuídas diretamente aos campos da estrutura. Esta abordagem é menos preferível, pois pode levar à inicialização incompleta do objeto.
type UserService struct { Repo UserRepository}
func main() { svc := &UserService{} svc.Repo = &PostgresRepository{}}3. Injeção via métodos (Method Injection)
A dependência é passada para o método, e não para o construtor. Útil para dependências temporárias.
type UserService struct{}
func (s *UserService) GetUser(repo UserRepository, id int) (*User, error) { return repo.FindByID(id)}Exemplo: construção de um servidor HTTP com DI
Vamos considerar um exemplo mais complexo — a criação de um servidor HTTP com injeção de dependências.
package main
import ( "encoding/json" "net/http")
// Definimos as interfacestype UserRepository int