Что это за библиотека и зачем нужна
fx — это мощный dependency injection (DI) фреймворк для языка Go, разработанный компанией Uber. Он предоставляет элегантный способ управления зависимостями в Go-приложениях, автоматизируя создание, связывание и жизненный цикл объектов.
В традиционном Go-приложении разработчики часто сталкиваются с проблемой "ада конструкторов" (constructor hell), когда для создания одного объекта требуется передать множество зависимостей. Это приводит к:
- Громоздкому коду с ручным управлением зависимостями
- Сложностям при тестировании
- Трудностям с изменением архитектуры приложения
fx решает эти проблемы, предоставляя:
- Автоматическое внедрение зависимостей — фреймворк сам определяет, какие объекты нужны и в каком порядке их создавать
- Управление жизненным циклом — автоматический запуск и остановка компонентов
- Модульность — приложение разбивается на независимые модули
- Тестируемость — легко подменять реальные зависимости на mock-объекты
fx особенно полезен при разработке:
- Микросервисов
- Веб-приложений
- CLI-инструментов
- Любых приложений со сложной архитектурой зависимостей
Установка
Установка fx осуществляется стандартным способом для Go-библиотек через go get:
go get go.uber.org/fx
Для обновления до последней версии:
go get -u go.uber.org/fx
После установки импортируйте библиотеку в свой код:
import "go.uber.org/fx"
Также рекомендуется установить утилиту fx для генерации кода (опционально):
go install go.uber.org/fx/cmd/fx@latest
Быстрый старт с примером кода
Рассмотрим простейший пример использования fx для создания HTTP-сервера:
package main
import (
"context"
"fmt"
"net/http"
"go.uber.org/fx"
)
// NewHandler создает HTTP-обработчик
func NewHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, fx!")
})
return mux
}
// NewServer создает HTTP-сервер
func NewServer(handler http.Handler) *http.Server {
return &http.Server{
Addr: ":8080",
Handler: handler,
}
}
// StartServer запускает сервер
func StartServer(lc fx.Lifecycle, server *http.Server) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
go server.ListenAndServe()
fmt.Println("Server started on :8080")
return nil
},
OnStop: func(ctx context.Context) error {
return server.Shutdown(ctx)
},
})
}
func main() {
app := fx.New(
fx.Provide(NewHandler),
fx.Provide(NewServer),
fx.Invoke(StartServer),
)
app.Run()
}
Этот пример демонстрирует базовую механику fx:
- fx.Provide — регистрирует конструкторы
- fx.Invoke — запускает функции, которые используют зависимости
- fx.Lifecycle — управляет жизненным циклом компонентов
Основные методы/функции
1. fx.New
Что делает: Создает новое приложение fx, которое управляет зависимостями. Это главная точка входа в фреймворк.
Параметры:
- options ...Option — вариативная функция, принимающая один или несколько параметров типа Option (fx.Provide, fx.Invoke, fx.Module и т.д.)
Рабочий пример кода:
package main
import (
"go.uber.org/fx"
)
type Config struct {
Port int
}
func NewConfig() *Config {
return &Config{Port: 8080}
}
func main() {
app := fx.New(
fx.Provide(NewConfig),
)
app.Run()
}
2. fx.Provide
Что делает: Регистрирует конструктор в контейнере зависимостей. Конструктор — это функция, которая возвращает один или несколько типов. fx автоматически создает экземпляр конструктора при необходимости.
Параметры:
- constructors ...interface{} — одна или несколько функций-конструкторов
Рабочий пример кода:
package main
import (
"go.uber.org/fx"
)
type Database struct {
ConnectionString string
}
type Logger struct {
Level string
}
func NewDatabase() *Database {
return &Database{ConnectionString: "postgres://localhost:5432"}
}
func NewLogger() *Logger {
return &Logger{Level: "info"}
}
func main() {
app := fx.New(
fx.Provide(NewDatabase),
fx.Provide(NewLogger),
)
app.Run()
}
3. fx.Invoke
Что делает: Регистрирует функцию, которая будет вызвана после того, как все зависимости будут готовы. Используется для запуска компонентов или выполнения инициализации.
Параметры:
- functions ...interface{} — одна или несколько функций, которые будут вызваны
Рабочий пример кода:
package main
import (
"fmt"
"go.uber.org/fx"
)
type Message string
func NewMessage() Message {
return "Hello from fx!"
}
func PrintMessage(msg Message) {
fmt.Println(msg)
}
func main() {
app := fx.New(
fx.Provide(NewMessage),
fx.Invoke(PrintMessage),
)
app.Run()
}
4. fx.Lifecycle
Что делает: Управляет жизненным циклом компонентов. Позволяет добавлять хуки, которые выполняются при запуске и остановке приложения. Это ключевой механизм для управления ресурсами.
Параметры:
- lc fx.Lifecycle — объект, который внедряется автоматически
Рабочий пример кода:
package main
import (
"context"
"fmt"
"go.uber.org/fx"
)
type Server struct{}
func NewServer(lc fx.Lifecycle) *Server {
server := &Server{}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
fmt.Println("Server starting...")
return nil
},
OnStop: func(ctx context.Context) error {
fmt.Println("Server stopping...")
return nil
},
})
return server
}
func main() {
app := fx.New(
fx.Provide(NewServer),
)
app.Run()
}
5. fx.Module
Что делает: Создает модуль — логически сгруппированный набор зависимостей. Модули позволяют организовывать приложение в независимые блоки, что улучшает читаемость и поддерживаемость кода.
Параметры:
- name string — имя модуля
- options ...Option — опции модуля (fx.Provide, fx.Invoke и т.д.)
Рабочий пример кода:
package main
import (
"go.uber.org/fx"
)
type Database struct{}
func NewDatabase() *Database {
return &Database{}
}
type Cache struct{}
func NewCache() *Cache {
return &Cache{}
}
func main() {
app := fx.New(
fx.Module("storage",
fx.Provide(NewDatabase),
fx.Provide(NewCache),
),
)
app.Run()
}
6. fx.Supply
Что делает: Предоставляет готовые значения в контейнер зависимостей, без использования конструктора. Полезно для передачи конфигураций или внешних объектов.
Параметры:
- values ...interface{} — одно или несколько значений для внедрения
Рабочий пример кода:
package main
import (
"fmt"
"go.uber.org/fx"
)
type Config struct {
Port int
Host string
}
func main() {
config := Config{Port: 8080, Host: "localhost"}
app := fx.New(
fx.Supply(config),
fx.Invoke(func(cfg Config) {
fmt.Printf("Server will run on %s:%d\n", cfg.Host, cfg.Port)
}),
)
app.Run()
}
7. fx.Annotate
Что делает: Позволяет аннотировать конструкторы или функции, добавляя дополнительную информацию для контейнера зависимостей. Используется для создания именованных зависимостей или указания групп.
Параметры:
- target interface{} — функция или значение для аннотирования
- annotations ...Annotation — аннотации (fx.ResultTags, fx.ParamTags)
Рабочий пример кода:
package main
import (
"fmt"
"go.uber.org/fx"
)
type Connection string
func main() {
type result struct {
fx.Out
Primary Connection `name:"primary"`
Secondary Connection `name:"secondary"`
}
app := fx.New(
fx.Provide(func() result {
return result{
Primary: "primary-db",
Secondary: "secondary-db",
}
}),
fx.Invoke(fx.Annotate(
func(primary Connection, secondary Connection) {
fmt.Printf("Primary: %s, Secondary: %s\n", primary, secondary)
},
fx.ParamTags(`name:"primary"`, `name:"secondary"`),
)),
)
app.Run()
}
8. fx.Decorate
Что делает: Декорирует существующую зависимость, позволяя изменить или расширить ее поведение. Полезно для добавления логирования, кэширования или других cross-cutting concerns.
Параметры:
- decorators ...interface{} — функции-декораторы
Рабочий пример кода:
package main
import (
"fmt"
"go.uber.org/fx"
)
type Service struct{}
func NewService() *Service {
return &Service{}
}
func (s *Service) Process() string {
return "Processing..."
}
func main() {
app := fx.New(
fx.Provide(NewService),
fx.Decorate(func(s *Service) *Service {
fmt.Println("Decorating service...")
return s
}),
fx.Invoke(func(s *Service) {
fmt.Println(s.Process())
}),
)
app.Run()
}
9. fx.Replace
Что делает: Заменяет существующую зависимость новой. Полезно для подмены зависимостей в тестах или при изменении конфигурации.
Параметры:
- values ...interface{} — новые значения для замены
Рабочий пример кода:
package main
import (
"fmt"
"go.uber.org/fx"
)
type Config struct {
Environment string
}
func main() {
app := fx.New(
fx.Supply(Config{Environment: "production"}),
fx.Replace(Config{Environment: "development"}),
fx.Invoke(func(cfg Config) {
fmt.Printf("Environment: %s\n", cfg.Environment)
}),
)
app.Run()
}
10. fx.Shutdowner
Что делает: Предоставляет интерфейс для программного завершения приложения. Позволяет инициировать graceful shutdown из любого компонента.
Параметры:
- shutdowner fx.Shutdowner — внедряется автоматически
Рабочий пример кода:
<