golang-uber-fx

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Persona: You are a Go architect building a long-running service with fx. You wire the graph at the composition root, push lifecycle into hooks instead of
init()
, and treat modules as the unit of reuse.
角色定位: 你是一名使用fx构建长期运行服务的Go架构师。你在组合根处编排依赖图,将生命周期逻辑移入钩子而非
init()
函数,并将模块作为复用单元。

Using uber-go/fx for Application Wiring in Go

使用uber-go/fx进行Go应用编排

Application framework combining a reflection-based DI container (built on
uber-go/dig
) with a lifecycle, module system, signal-aware run loop, and structured event logging. For long-running services where boot order, graceful shutdown, and modular composition matter.
Official Resources:
This skill is not exhaustive. Please refer to library documentation and code examples for more information. Context7 can help as a discoverability platform.
bash
go get go.uber.org/fx
fx vs. dig. fx wraps dig and adds lifecycle hooks (
fx.Lifecycle
), modules (
fx.Module
),
Run()
with signal handling, structured event logs (
fxevent
), and ergonomic helpers. Use raw dig (
samber/cc-skills-golang@golang-uber-dig
skill) only when you don't need lifecycle or app boot — most production services should use fx.
该应用框架结合了基于反射的DI容器(构建于
uber-go/dig
之上)、生命周期管理、模块系统、支持信号处理的运行循环以及结构化事件日志功能。适用于启动顺序、优雅关闭和模块化组合至关重要的长期运行服务。
官方资源:
本技能内容并非详尽无遗。如需更多信息,请参考库文档和代码示例。Context7可作为发现平台提供帮助。
bash
go get go.uber.org/fx
fx vs. dig:fx封装了dig,并添加了生命周期钩子(
fx.Lifecycle
)、模块(
fx.Module
)、带信号处理的
Run()
方法、结构化事件日志(
fxevent
)以及易用的辅助工具。仅当不需要生命周期或应用启动管理时才使用原生dig(
samber/cc-skills-golang@golang-uber-dig
技能)——大多数生产服务应使用fx。

The Application

应用示例

go
import "go.uber.org/fx"

app := fx.New(
    fx.Provide(NewLogger, NewDatabase, NewServer),
    fx.Invoke(RegisterRoutes),
)
app.Run() // blocks until SIGINT/SIGTERM, then runs OnStop hooks
Boot stages:
fx.New
validates types (constructors do not run);
app.Start(ctx)
runs each
fx.Invoke
and fires OnStart hooks in topological order; main blocks on
app.Done()
;
app.Stop(ctx)
fires OnStop hooks in reverse order. Default timeout is 15 seconds — override with
fx.StartTimeout
/
fx.StopTimeout
.
go
import "go.uber.org/fx"

app := fx.New(
    fx.Provide(NewLogger, NewDatabase, NewServer),
    fx.Invoke(RegisterRoutes),
)
app.Run() // 阻塞直到收到SIGINT/SIGTERM信号,随后执行OnStop钩子
启动阶段:
fx.New
验证类型(构造函数不会运行);
app.Start(ctx)
按拓扑顺序执行每个
fx.Invoke
并触发OnStart钩子;主线程阻塞在
app.Done()
app.Stop(ctx)
按逆序触发OnStop钩子。默认超时时间为15秒——可通过
fx.StartTimeout
/
fx.StopTimeout
覆盖。

Provide and Invoke

Provide与Invoke

go
fx.New(
    fx.Provide(NewLogger, NewDatabase, NewServer),  // lazy
    fx.Invoke(RegisterRoutes, StartMetricsExporter), // always run during Start
)
fx.Provide
registers constructors;
fx.Invoke
is the trigger — without an Invoke (directly or transitively) referencing a type, its constructor never runs.
go
fx.New(
    fx.Provide(NewLogger, NewDatabase, NewServer),  // 延迟初始化
    fx.Invoke(RegisterRoutes, StartMetricsExporter), // 启动阶段始终执行
)
fx.Provide
注册构造函数;
fx.Invoke
是触发点——如果没有Invoke(直接或间接)引用某个类型,其构造函数永远不会运行。

Lifecycle Hooks

生命周期钩子

Inject
fx.Lifecycle
and append hooks. Constructors should return quickly; long-running work belongs in
OnStart
.
go
func NewHTTPServer(lc fx.Lifecycle, log *zap.Logger, cfg *Config) *http.Server {
    srv := &http.Server{Addr: cfg.Addr}

    lc.Append(fx.Hook{
        OnStart: func(ctx context.Context) error {
            ln, err := net.Listen("tcp", srv.Addr)
            if err != nil { return err }
            go srv.Serve(ln)         // blocking work in a goroutine
            return nil
        },
        OnStop: func(ctx context.Context) error {
            return srv.Shutdown(ctx)
        },
    })
    return srv
}
Both callbacks receive a context bounded by
StartTimeout
/
StopTimeout
— respect cancellation. OnStart must return quickly — spawn a goroutine for blocking work; otherwise startup hangs and dependent hooks never fire.
fx.StartHook
/
fx.StopHook
/
fx.StartStopHook
adapt simpler signatures (no context, no error, or both):
go
lc.Append(fx.StartStopHook(srv.Start, srv.Stop))   // matched pair
注入
fx.Lifecycle
并添加钩子。构造函数应快速返回;长期运行的任务应放在
OnStart
中。
go
func NewHTTPServer(lc fx.Lifecycle, log *zap.Logger, cfg *Config) *http.Server {
    srv := &http.Server{Addr: cfg.Addr}

    lc.Append(fx.Hook{
        OnStart: func(ctx context.Context) error {
            ln, err := net.Listen("tcp", srv.Addr)
            if err != nil { return err }
            go srv.Serve(ln)         // 在goroutine中执行阻塞任务
            return nil
        },
        OnStop: func(ctx context.Context) error {
            return srv.Shutdown(ctx)
        },
    })
    return srv
}
两个回调函数都会接收受
StartTimeout
/
StopTimeout
限制的context——请遵守取消机制。OnStart必须快速返回——将阻塞任务放在goroutine中;否则启动过程会挂起,依赖的钩子永远不会触发。
fx.StartHook
/
fx.StopHook
/
fx.StartStopHook
可适配更简单的签名(无context、无错误或两者都无):
go
lc.Append(fx.StartStopHook(srv.Start, srv.Stop))   // 匹配的钩子对

Parameter and Result Objects

参数与结果对象

fx re-exports dig's
dig.In
/
dig.Out
as
fx.In
/
fx.Out
. Use them when a constructor has 4+ dependencies, or when you need
name
/
group
/
optional
tags.
go
type ServerParams struct {
    fx.In

    Logger *zap.Logger
    DB     *sql.DB
    Cache  *redis.Client     `optional:"true"`
    Routes []http.Handler    `group:"routes"`
}

func NewServer(p ServerParams) *Server { /* ... */ }
fx将dig的
dig.In
/
dig.Out
重新导出为
fx.In
/
fx.Out
。当构造函数有4个及以上依赖,或需要
name
/
group
/
optional
标签时使用它们。
go
type ServerParams struct {
    fx.In

    Logger *zap.Logger
    DB     *sql.DB
    Cache  *redis.Client     `optional:"true"`
    Routes []http.Handler    `group:"routes"`
}

func NewServer(p ServerParams) *Server { /* ... */ }

fx.Annotate

fx.Annotate

fx.Annotate
wraps a constructor to add tags or interface bindings without a
fx.Out
struct. Prefer it for ergonomic name/group/As bindings:
go
fx.Provide(
    fx.Annotate(NewPrimaryDB, fx.ResultTags(`name:"primary"`)),
    fx.Annotate(NewPostgresDB, fx.As(new(Database))),    // expose interface
    fx.Annotate(NewUserHandler,
        fx.As(new(http.Handler)),
        fx.ResultTags(`group:"routes"`),
    ),
)
fx.Annotate
封装构造函数,无需
fx.Out
结构体即可添加标签或接口绑定。推荐用于便捷的名称/分组/As绑定:
go
fx.Provide(
    fx.Annotate(NewPrimaryDB, fx.ResultTags(`name:"primary"`)),
    fx.Annotate(NewPostgresDB, fx.As(new(Database))),    // 暴露接口
    fx.Annotate(NewUserHandler,
        fx.As(new(http.Handler)),
        fx.ResultTags(`group:"routes"`),
    ),
)

Value Groups

值分组

Many constructors, one consumer slice — typical for routes, health checks, metrics collectors:
go
type RouteResult struct {
    fx.Out
    Handler http.Handler `group:"routes"`
}

type ServerParams struct {
    fx.In
    Routes []http.Handler `group:"routes"`
}
Append
,flatten
(
group:"routes,flatten"
) to unwrap a slice instead of nesting it. Order is not guaranteed — provide an explicit ordered slice when sequence matters.
多个构造函数对应一个消费者切片——典型场景如路由、健康检查、指标收集器:
go
type RouteResult struct {
    fx.Out
    Handler http.Handler `group:"routes"`
}

type ServerParams struct {
    fx.In
    Routes []http.Handler `group:"routes"`
}
添加
,flatten
group:"routes,flatten"
)可展开切片而非嵌套。顺序不保证——当顺序重要时,需从单个构造函数提供显式有序切片。

fx.Module

fx.Module

fx.Module
groups providers, invokes, and decorators under a name. Modules scope decorators to themselves and their children — a logger renamed in
fx.Module("db", ...)
only appears renamed for code inside that module.
go
var DatabaseModule = fx.Module("database",
    fx.Provide(NewConnection, NewUserRepository),
    fx.Decorate(func(log *zap.Logger) *zap.Logger {
        return log.Named("db")
    }),
)

func main() {
    fx.New(
        fx.Provide(NewConfig, NewLogger),
        DatabaseModule,
        HTTPModule,
    ).Run()
}
Treat each module as a small library that can be lifted into another app — its public surface is the types it Provides.
For
fx.Supply
/
fx.Replace
/
fx.Decorate
, optional deps, custom logging, manual lifecycle, and Quick Reference, see advanced.md.
fx.Module
将提供者、调用函数和装饰器归到一个名称下。模块限定装饰器的作用域仅到自身及其子模块——在
fx.Module("db", ...)
中重命名的日志器仅对该模块内的代码生效。
go
var DatabaseModule = fx.Module("database",
    fx.Provide(NewConnection, NewUserRepository),
    fx.Decorate(func(log *zap.Logger) *zap.Logger {
        return log.Named("db")
    }),
)

func main() {
    fx.New(
        fx.Provide(NewConfig, NewLogger),
        DatabaseModule,
        HTTPModule,
    ).Run()
}
将每个模块视为可移植到其他应用的小型库——它对外暴露的类型即为其公共接口。
关于
fx.Supply
/
fx.Replace
/
fx.Decorate
、可选依赖、自定义日志、手动生命周期管理和快速参考,请查看advanced.md

Best Practices

最佳实践

  1. Keep
    main()
    thin — providers, modules, and a single
    Run()
    . Push real work into modules so each can be tested in isolation.
  2. Use lifecycle hooks instead of
    init()
    or goroutines launched from constructors — Start/Stop ordering depends on graph topology, but
    init()
    goroutines do not, which leads to races and leaks.
  3. OnStart must return promptly — long work goes in a goroutine inside the hook. A blocking OnStart hangs the rest of the boot.
  4. Respect
    ctx.Done()
    in hooks — a hook that ignores cancellation is reported as a timeout failure but its goroutine continues, leaking resources.
  5. Group by module, not by layer — a module owns the providers, lifecycle, and decorators for one concern (HTTP, DB, metrics).
  6. Use
    fx.Annotate
    for tags rather than wrapping a constructor in an
    fx.Out
    struct — keeps the constructor reusable outside fx.
  7. Replace
    fx.Provide
    with
    fx.Supply
    for pre-built values (config, command-line flags). Shorter, signals intent.
  8. Validate the graph in CI by booting under
    fx.New(...).Err()
    — catches missing providers and cycles before deploy.
  1. 保持
    main()
    简洁——仅包含提供者、模块和单个
    Run()
    。将实际业务逻辑移入模块,以便每个模块可独立测试。
  2. 使用生命周期钩子而非
    init()
    或从构造函数启动的goroutine——启动/停止顺序依赖于依赖图拓扑,但
    init()
    中的goroutine不遵循此顺序,会导致竞态条件和资源泄漏。
  3. OnStart必须及时返回——长期运行的任务应放在钩子内的goroutine中。阻塞的OnStart会导致整个启动过程挂起。
  4. 在钩子中遵守
    ctx.Done()
    ——忽略取消机制的钩子会被报告为超时失败,但其goroutine仍会继续运行,造成资源泄漏。
  5. 按模块分组而非按分层——每个模块负责一个关注点(HTTP、数据库、指标)的提供者、生命周期和装饰器。
  6. 使用
    fx.Annotate
    添加标签,而非用
    fx.Out
    结构体封装构造函数——这样可保持构造函数在fx之外的复用性。
  7. 对于预构建值(配置、命令行参数),用
    fx.Supply
    替代
    fx.Provide
    。更简洁,且能明确表达意图。
  8. 在CI中通过
    fx.New(...).Err()
    启动应用来验证依赖图——在部署前捕获缺失的提供者和循环依赖。

Common Mistakes

常见错误

MistakeFix
Long-running work directly in OnStartSpawn a goroutine inside OnStart; the hook itself must return quickly so dependent hooks can run.
fx.Provide
something that should be
fx.Supply
Pre-built values (config, secrets) belong in
fx.Supply
— clearer and avoids a no-op constructor.
Module decorator leaking to siblingsDecorate inside
fx.Module(...)
— decorators flow only to descendants. A top-level
fx.Decorate
is global.
Group order assumedGroups are unordered. If order matters, provide an ordered slice from one constructor.
Constructors with side effectsSide effects belong in OnStart — constructors should be cheap and pure-ish, since they may run concurrently and lazily.
Forgotten
fx.Invoke
Without an Invoke (or downstream consumer), constructors never run. Add at least one Invoke per app.
错误修复方案
在OnStart中直接执行长期运行的任务在OnStart内部启动goroutine;钩子本身必须快速返回,以便依赖的钩子可以运行。
fx.Provide
提供应使用
fx.Supply
的内容
预构建值(配置、密钥)应放在
fx.Supply
中——更清晰,且避免无操作的构造函数。
模块装饰器影响到同级模块
fx.Module(...)
内部进行装饰——装饰器仅作用于后代模块。顶层的
fx.Decorate
是全局生效的。
假设分组顺序是固定的分组是无序的。如果顺序重要,从单个构造函数提供显式有序的切片。
构造函数带有副作用副作用应放在OnStart中——构造函数应轻量化且接近纯函数,因为它们可能并发且延迟执行。
忘记添加
fx.Invoke
如果没有Invoke(或下游消费者),构造函数永远不会运行。每个应用至少添加一个Invoke。

Testing

测试

Use
go.uber.org/fx/fxtest
to integrate fx with
*testing.T
(failures call
t.Fatal
,
RequireStop
registers as
t.Cleanup
).
fx.Populate(&target)
pulls values out of the graph;
fx.Replace
swaps real dependencies for fakes. Full patterns in testing.md.
使用
go.uber.org/fx/fxtest
将fx与
*testing.T
集成(失败时调用
t.Fatal
RequireStop
注册为
t.Cleanup
)。
fx.Populate(&target)
从依赖图中提取值;
fx.Replace
将真实依赖替换为模拟实现。完整模式请查看testing.md

Further Reading

扩展阅读

  • advanced.md — Supply/Replace/Decorate, optional deps, custom event logging, manual lifecycle, full Quick Reference
  • recipes.md — full HTTP service with database/metrics, background workers with graceful drain, multiple impls of the same interface, manual lifecycle for CLI embedding
  • testing.md — fxtest patterns,
    fx.Replace
    ,
    fx.Populate
    , isolated lifecycle tests, CI graph validation
  • advanced.md —— Supply/Replace/Decorate、可选依赖、自定义事件日志、手动生命周期管理、完整快速参考
  • recipes.md —— 包含数据库/指标的完整HTTP服务、支持优雅关闭的后台工作器、同一接口的多个实现、用于CLI嵌入的手动生命周期管理
  • testing.md —— fxtest模式、
    fx.Replace
    fx.Populate
    、独立生命周期测试、CI依赖图验证

Cross-References

交叉参考

  • → See
    samber/cc-skills-golang@golang-uber-dig
    skill for the underlying container,
    dig.In
    /
    dig.Out
    , and DI without lifecycle
  • → See
    samber/cc-skills-golang@golang-dependency-injection
    skill for DI concepts and library comparison
  • → See
    samber/cc-skills-golang@golang-samber-do
    skill for a generics-based alternative without reflection
  • → See
    samber/cc-skills-golang@golang-google-wire
    skill for compile-time DI (no runtime container)
  • → See
    samber/cc-skills-golang@golang-structs-interfaces
    skill for interface design patterns
  • → See
    samber/cc-skills-golang@golang-context
    skill for context propagation in OnStart/OnStop hooks
  • → See
    samber/cc-skills-golang@golang-testing
    skill for general testing patterns
If you encounter a bug or unexpected behavior in uber-go/fx, open an issue at https://github.com/uber-go/fx/issues.
  • → 如需底层容器、
    dig.In
    /
    dig.Out
    以及无生命周期的DI方案,请查看
    samber/cc-skills-golang@golang-uber-dig
    技能
  • → 如需DI概念和库对比,请查看
    samber/cc-skills-golang@golang-dependency-injection
    技能
  • → 如需基于泛型的无反射替代方案,请查看
    samber/cc-skills-golang@golang-samber-do
    技能
  • → 如需编译时DI(无运行时容器),请查看
    samber/cc-skills-golang@golang-google-wire
    技能
  • → 如需接口设计模式,请查看
    samber/cc-skills-golang@golang-structs-interfaces
    技能
  • → 如需OnStart/OnStop钩子中的context传播,请查看
    samber/cc-skills-golang@golang-context
    技能
  • → 如需通用测试模式,请查看
    samber/cc-skills-golang@golang-testing
    技能
如果在使用uber-go/fx时遇到bug或意外行为,请在https://github.com/uber-go/fx/issues提交问题。