Mastering Advanced fx Features: Invoke, Supply, Populate, and More
This article dives deep into Uber's fx dependency‑injection framework for Go, explaining advanced constructs such as fx.Invoke, fx.Supply, fx.Populate, fx.Annotated, fx.In, fx.Out, fx.Module, fx.WithLogger and fx.As, and provides complete code examples that illustrate how to manage lifecycles, inject static values, differentiate multiple implementations, and customize logging in production‑grade applications.
fx.Invoke
The fx.Invoke option registers functions that are executed when the application starts. These functions automatically receive any required dependencies from the fx container, similar to Spring Boot's CommandLineRunner but with lazy initialization. The example creates a simple main that provides an Age struct, a zap.Logger, and a Tester struct, then registers an anonymous function via fx.Invoke to ensure the Tester (and its dependencies) are instantiated at startup.
package main
import (
"go.uber.org/fx"
"go.uber.org/zap"
)
type Age struct { Num int }
type Tester struct { Log *zap.Logger; Age *Age }
func NewTester(age *Age, log *zap.Logger) *Tester { return &Tester{Age: age, Log: log} }
func main() {
app := fx.New(
fx.Provide(
NewTester,
func() *Age { return &Age{Num: 18} },
func() (*zap.Logger, error) { return zap.NewProduction() },
),
fx.Invoke(func(*Tester) {}),
)
app.Run()
}fx.Supply
fx.Supplyinjects a concrete value directly into the container without needing a provider function. Typical scenarios include supplying static configuration objects, pre‑created instances, or test doubles.
package main
import (
"go.uber.org/fx"
"go.uber.org/zap"
)
type Age struct { Num int }
type Tester struct { Log *zap.Logger; Age *Age }
func NewTester(age *Age, log *zap.Logger) *Tester { return &Tester{Age: age, Log: log} }
func main() {
app := fx.New(
fx.Provide(func() (*zap.Logger, error) { return zap.NewProduction() }),
fx.Supply(&Age{Num: 18}),
)
app.Run()
}fx.Populate
fx.Populatecopies values from the fx container into external variables, which is useful for global variables or testing setups. The targets must be pointers to the concrete types.
package main
import (
"go.uber.org/fx"
"go.uber.org/zap"
)
var (
logger *zap.Logger
age *Age
)
func main() {
app := fx.New(
fx.Provide(NewLogger, NewAge),
fx.Populate(&logger, &age),
fx.Invoke(func() { logger.Info("Application started", zap.Int("age", age.Num)) }),
)
app.Run()
}
func NewLogger() (*zap.Logger, error) { return zap.NewProduction() }
func NewAge() *Age { return &Age{Num: 30} }
type Age struct { Num int }fx.Annotated
fx.Annotated(used together with fx.Annotate) lets you tag constructors so that multiple providers of the same type can be distinguished, for example providing an "old" and a "young" Age implementation.
package main
import (
"go.uber.org/fx"
"go.uber.org/zap"
)
type Age struct { Num int }
type Tester struct { Log *zap.Logger; Age *Age }
var Ages = fx.Provide(
fx.Annotated{ Name: "old", Target: NewAgeOld },
fx.Annotated{ Name: "young", Target: NewAgeYoung },
)
func main() {
app := fx.New(
fx.Provide(NewLogger),
fx.Annotate(NewTester, fx.ParamTags(`name:"old"`)),
Ages,
fx.Invoke(func(t *Tester) { t.Log.Info("success", zap.Int("age", t.Age.Num)) }),
)
app.Run()
}
func NewLogger() (*zap.Logger, error) { return zap.NewProduction() }
func NewTester(age *Age, log *zap.Logger) *Tester { return &Tester{Age: age, Log: log} }
func NewAgeYoung() *Age { return &Age{Num: 18} }
func NewAgeOld() *Age { return &Age{Num: 60} }fx.In
fx.Inaggregates several dependencies into a single struct, allowing optional fields, tag‑based selection, and cleaner constructor signatures.
package main
import (
"fmt"
"go.uber.org/fx"
)
type Name struct { Str string }
type Age struct { Num int }
type Tester struct {
fx.In
Age *Age
Name *Name
}
func main() {
app := fx.New(
fx.Provide(
func() *Age { return &Age{Num: 30} },
func() *Name { return &Name{Str: "FunTester"} },
),
fx.Invoke(func(t Tester) { fmt.Println(t.Name.Str) }),
)
app.Run()
}fx.Out
fx.Outenables a constructor to return multiple values that the container can inject elsewhere, optionally using tags to name or group them.
package main
import (
"fmt"
"go.uber.org/fx"
)
type Name struct { Str string }
type Age struct { Num int }
type Values struct {
fx.Out
Age *Age
Name *Name
}
func NewValues() Values { return Values{Age: &Age{Num: 30}, Name: &Name{Str: "FunTester"}} }
func main() {
app := fx.New(
fx.Provide(NewValues),
fx.Invoke(func(age *Age) { fmt.Println(age.Num) }),
)
app.Run()
}fx.Module
fx.Modulegroups related providers and invokers into a reusable unit that can be nested or conditionally loaded, simplifying the structure of large Go applications.
fx.WithLogger
fx.WithLoggerlets you replace the default fx logger with a custom implementation such as zap.Logger, providing consistent logging across the application.
fx.As
fx.Asconverts a concrete type to an interface during fx.Provide, allowing more flexible and testable dependency injection.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
