Dependency Injection контейнер для Go (Golang): Wire, Dig и Uber FX
Dependency Injection (DI) — это паттерн проектирования, при котором объект получает свои зависимости извне, а не создаёт их сам. В Go (Golang) DI особенно полезен для создания модульного, тестируемого и масштабируемого кода. Наиболее популярные инструменты DI для Go — это Google Wire, Uber Dig и Uber FX. В этой статье мы рассмотрим каждый из них, их особенности и примеры использования.
Зачем нужен Dependency Injection в Go?
Go — статически типизированный язык с явной передачей параметров. Без DI разработчики часто создают глобальные переменные или фабрики, что усложняет тестирование и поддержку. DI контейнеры решают эти проблемы:
- Слабая связанность — компоненты зависят от интерфейсов, а не от конкретных реализаций.
- Тестируемость — легко подменять зависимости на моки.
- Упрощение конфигурации — контейнер автоматически разрешает граф зависимостей.
Google Wire — генерация кода во время компиляции
Wire от Google — это не runtime-контейнер, а генератор кода. Он анализирует функции-конструкторы и генерирует код для связывания зависимостей на этапе компиляции. Это даёт нулевую нагрузку во время выполнения и раннее обнаружение ошибок.
Установка Wire
go install github.com/google/wire/cmd/wire@latest
Пример использования Wire
Предположим, у нас есть сервис, который зависит от базы данных и логгера:
// main.go
package main
import "fmt"
type Logger struct{}
func NewLogger() Logger { return Logger{} }
func (l Logger) Log(msg string) { fmt.Println(msg) }
type Database struct {
logger Logger
}
func NewDatabase(logger Logger) Database {
return Database{logger: logger}
}
type Service struct {
db Database
log Logger
}
func NewService(db Database, log Logger) Service {
return Service{db: db, log: log}
}
Теперь создадим файл wire.go с инструкцией для Wire:
// wire.go
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
func InitializeService() Service {
wire.Build(NewLogger, NewDatabase, NewService)
return Service{}
}
Запустим генерацию:
wire
Wire создаст файл wire_gen.go с готовым кодом инициализации:
// wire_gen.go
// Code generated by Wire. DO NOT EDIT.
package main
func InitializeService() Service {
logger := NewLogger()
database := NewDatabase(logger)
service := NewService(database, logger)
return service
}
Плюсы Wire:
- Ошибки на этапе компиляции.
- Нет reflection, высокая производительность.
- Простой и предсказуемый код.
Минусы Wire:
- Требует отдельного шага генерации.
- Сложно использовать с динамическими зависимостями.
Uber Dig — runtime-контейнер с reflection
Dig от Uber — это контейнер, работающий во время выполнения. Он использует reflection для анализа типов и автоматического разрешения зависимостей. Dig предоставляет простой API для регистрации конструкторов и извлечения объектов.
Установка Dig
go get go.uber.org/dig
Пример использования Dig
package main
import (
"fmt"
"go.uber.org/dig"
)
type Config struct {
Port int
}
func NewConfig() *Config {
return &Config{Port: 8080}
}
type Server struct {
config *Config
}
func NewServer(c *Config) *Server {
return &Server{config: c}
}
func (s *Server) Start() {
fmt.Printf("Server started on port %d\n", s.config.Port)
}
func main() {
container := dig.New()
// Регистрируем конструкторы
container.Provide(NewConfig)
container.Provide(NewServer)
// Извлекаем и запускаем сервер
container.Invoke(func(s *Server) {
s.Start()
})
}
Плюсы Dig:
- Простой и интуитивный API.
- Поддержка групп зависимостей (например, несколько реализаций интерфейса).
- Работает в runtime, не требует генерации.
Минусы Dig:
- Использует reflection, что может снизить производительность.
- Ошибки проявляются только во время выполнения.