fx для Go: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что это за библиотека и зачем нужна

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 — внедряется автоматически

Рабочий пример кода:

<

Рекомендаций