Uber Fx 依赖注入框架学习笔记

1. 什么是 Uber Fx?

uber-go/fx 是 Uber 开源的一个 Go 语言依赖注入(Dependency Injection,简称 DI)框架。它旨在简化 Go 应用程序的初始化、配置和生命周期管理。

核心功能

  • 依赖注入: 自动解决组件之间的依赖关系,无需手动编写繁琐的初始化代码。
  • 生命周期管理: 提供 OnStartOnStop 钩子,管理应用的启动和关闭流程。
  • 模块化: 鼓励将应用拆分为可复用的模块 (fx.Module)。

2. 为什么要用 Fx?

在没有 DI 框架时,我们的 main 函数通常是这样的(“手动挡”):

func main() {
    // 手动管理依赖顺序
    config := NewConfig()
    db := NewDatabase(config)        // db 依赖 config
    logger := NewLogger(config)      // logger 依赖 config
    service := NewService(db, logger) // service 依赖 db 和 logger
    server := NewServer(service)     // server 依赖 service
    
    server.Start()
}

痛点:

  1. 顺序耦合: 必须严格按照依赖顺序初始化,写错顺序会报错。
  2. 修改困难: 如果 NewService 增加了一个参数,所有调用它的地方都要改。
  3. 全局状态: 容易导致滥用全局变量(init 函数)。

Fx 的优势 (“自动挡”):

  • 声明式: 你只需要告诉 Fx “我有这个构造函数”,Fx 会自动计算依赖图并按需初始化。
  • 解耦: 组件之间只通过接口交互,不需要知道具体的创建过程。

3. Fx 的核心概念与使用

3.1 fx.Provide: 注册提供者 (构造函数)

告诉 Fx 如何创建对象。Fx 通过函数的返回值类型来建立索引。

// 构造函数:提供 Greeter 接口
// Fx 记录:Greeter (接口) -> 由 NewGreeter 函数提供
func NewGreeter() Greeter {
    return &ConsoleGreeter{Name: "Console Greeter"}
}

// 构造函数:提供 *User 实例
// Fx 记录:*User (类型) -> 由 NewUser 函数提供
// Fx 发现参数需要 Greeter,会自动去查找谁提供了 Greeter
func NewUser(g Greeter) *User {
    return &User{Greeter: g}
}

// 在 main 中注册
fx.Provide(NewGreeter, NewUser)

关键点:

  • 接口绑定: 如果构造函数返回接口类型(如 Greeter),Fx 就能注入给需要该接口的地方。
  • 类型严格: 如果返回 *ConsoleGreeter,Fx 就只能注入给需要 *ConsoleGreeter 的地方,不能自动注入给 Greeter 接口。

3.2 fx.Invoke: 触发注入 (入口函数)

告诉 Fx 从哪里开始执行。通常用于启动服务器、注册处理器或执行一次性任务。

// 这是一个普通的函数,作为入口
// Fx 看到参数 *User,会自动创建并注入
func RegisterUser(lifecycle fx.Lifecycle, u *User) {
    // ...
}

// 在 main 中调用
fx.Invoke(RegisterUser)

执行流程:

  1. Fx 看到 Invoke(RegisterUser)
  2. 检查 RegisterUser 参数 -> 需要 *User
  3. 查找 *User 提供者 -> NewUser
  4. 检查 NewUser 参数 -> 需要 Greeter
  5. 查找 Greeter 提供者 -> NewGreeter
  6. 开始执行: NewGreeter() -> NewUser() -> RegisterUser()

3.3 fx.Lifecycle: 生命周期管理

允许你介入应用的启动和停止过程。

func RegisterUser(lifecycle fx.Lifecycle, u *User) {
    lifecycle.Append(fx.Hook{
        // OnStart: 应用启动时执行 (app.Run() 之后)
        // context 包含启动超时时间
        OnStart: func(ctx context.Context) error {
            fmt.Println("应用启动...")
            u.Greeter.SayHello()
            return nil
        },
        // OnStop: 应用停止时执行 (Ctrl+C 或 app.Stop())
        // 用于清理资源,如关闭数据库连接
        OnStop: func(ctx context.Context) error {
            fmt.Println("应用停止...")
            return nil
        },
    })
}

3.4 fx.New vs app.Run

  • fx.New(...): 配置阶段。定义依赖图,注册构造函数和入口函数。此时不会创建实例。
  • app.Run(): 运行阶段。解析依赖,创建实例,执行 Invoke,运行 OnStart,然后阻塞等待停止信号。