FunTester fx 框架上手 - 基础篇

FunTester · 2024年07月29日 · 2403 次阅读

在现代软件开发中,依赖注入(Dependency Injection,简称 DI)已成为一种不可或缺的设计模式和编程范式。它不仅能够提高代码的可维护性和可测试性,还能帮助开发者构建更加灵活、松耦合的系统。本文将带您深入了解依赖注入的核心概念,探讨它如何改变我们设计和实现软件的方式,并通过实际的代码示例,展示如何在项目中有效地应用这一技术。

相信各位对 依赖注入 不会陌生,相信大多数使用 Java 或者其他 JVM 语言作为主力语言的测试同行来说,更多经验是集中在 Spring 框架学习和使用当中。在JavaSpring框架中,依赖注入是构建灵活、可维护应用程序的核心技术。SpringIoC容器通过构造器注入、Setter注入或字段注入等方式自动管理对象间的依赖关系。开发者使用@Autowired@Component等注解或 XML 配置来声明依赖和组件,让 Spring 负责对象的创建和生命周期管理。这种方法不仅简化了代码结构,还提高了应用的可测试性和模块化程度,使得 Java 开发者能够专注于业务逻辑的实现,而不必手动处理复杂的对象依赖关系。

而对于 Go 语言来说,显然没有类似 Spring 一统天下的局面。对于提升编程效率,可以说是百家争鸣,好不热闹。在 Go 语言中,虽然没有像 JavaC# 那样内置的依赖注入框架,但依赖注入的需求同样存在。开发者通常需要手动注入依赖项,这种方式在应用规模扩大后变得繁琐且易出错。fx 框架提供了一种自动化和模块化的依赖注入方式,使开发者可以更专注于业务逻辑,而不是依赖管理。

其中 fx 显得比较耀眼夺目,下面我们来开始 fx 框架的学习。

fx 介绍

fx 框架是由 Uber 开发的。为了应对自身复杂的分布式系统和微服务架构的需求,Uber 开发了许多开源工具和框架,其中包括 fxfx 框架主要用于简化 Go 语言应用程序的依赖注入和生命周期管理,并且已经在 Uber 内部和外部的许多项目中得到了广泛应用。

fx 框架是一个用于构建 Go 应用程序的依赖注入框架,它简化了应用程序的初始化、启动和停止过程。fx 通过自动管理依赖关系,使开发者能够专注于业务逻辑,而无需手动处理依赖注入。fx 提供了模块化的依赖注入方式,通过 fx.Provide 注册依赖项,通过 fx.Invoke 调用需要的组件。同时,fx.Infx.Out 结构体帮助开发者更方便地声明和管理依赖项,支持按名称和分组注入。fx.Lifecyclefx 的核心功能之一,它允许开发者在应用程序的不同生命周期阶段执行特定逻辑。通过 fx.Hook,可以在应用启动和停止时执行初始化和清理操作,如连接数据库、启动后台任务等。

fx 的模块化设计使其易于扩展和维护,通过将各个功能模块化,开发者可以灵活地组合和重用不同的组件。总之,fx 提供了简洁且强大的工具,使得构建复杂的 Go 应用程序更加高效和可维护。

fx 依赖注入

首先我们来先看一个 fx 框架启动的例子。

func main() {  
    app := fx.New() //创建一个fx.App实例  
    app.Run()       //运行fx.App实例  
}

这是一个标准的语法,所有的应用都可以用这个语法进行创建和启动。其中 fx.New() 方法为: func New(opts ...Option) *App {} ,其中参数 Option 是我们后面主要学习对象。

说到 依赖注入 ,我首先意识到两个概念,就是依赖对象的提供者和使用者。得有一些对象的创建需要依赖其他对象,然后还需要提供被依赖对象,然后通过 fx 框架将这些复杂的逻辑关系进行管理,并且提供简单的 API 给用户。

fx 的核心功能是依赖注入,它简化了依赖项的管理和注入过程,主要通过以下 API 实现:

  • fx.Provide:用于注册提供者函数,这些函数会返回应用程序中需要的依赖项。

下面我们通过一个例子来演示 fx 如何进行简单依赖注入:

package main  

import (  
    "go.uber.org/fx"  
    "go.uber.org/zap")  

func main() {  
    app := fx.New( //创建fx.App实例  
       fx.Provide(NewTester, func() *Age {  
          return &Age{Num: 18} //提供Age实例  
       }, func() *zap.Logger {  
          production, _ := zap.NewProduction() //提供zap.Logger实例  
          return production  
       }), //提供NewTester函数  
    )  
    app.Run() //运行fx.App实例  
}  

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,  
    }  

}

这段代码展示了如何使用 UberFx 框架进行依赖注入和应用程序启动。代码通过 fx.Provide 提供了三个构造函数:一个用于 Age 实例,一个用于 zap.Logger 实例,另一个用于 Tester 实例。然后通过 fx.New 创建一个 Fx 应用,并通过 app.Run() 运行这个应用。下面是更详细的解释:

  1. 依赖注入
    • fx.Provide(NewTester, ...):通过 fx.Provide 注册三个构造函数。
    • NewTester:这是一个构造函数,接受 *Age*zap.Logger 作为参数,并返回一个 *Tester
    • 匿名函数 func() *Age:提供一个 *Age 实例,设置 Num18
    • 匿名函数 func() *zap.Logger:创建并返回一个 zap.Logger 实例,用于日志记录。
  2. 运行应用
    • app.Run():启动 Fx 应用。Fx 将根据注册的构造函数自动注入依赖,并调用相应的初始化逻辑。
  3. 类型定义
    • Age:一个简单的结构体,包含一个 Num 字段,用于表示年龄。
    • Tester:一个结构体,包含两个字段:Log(类型为 *zap.Logger)和 Age(类型为 *Age)。它们将由 Fx 框架自动注入。
  4. 构造函数 NewTester
    • 接受 *Age*zap.Logger 作为参数,并返回一个 *Tester 实例。

这个例子中,既可以将创建方法传给 fx.Provide 也可以使用匿名方法,相比较来说是灵活的。这里不建议使用匿名方法,因为写多了容易乱,特别是对于 zap.Logger 这种对象来讲,真实的创建代码可能超过 20 行,用匿名方法更是灾难了。

生命周期管理

fx.LifecycleUber Fx 框架中用于管理应用程序生命周期的一部分。它允许你在应用程序的启动和停止阶段执行特定的逻辑。fx.Lifecycle 提供了一种添加启动和停止钩子的机制,使你能够在应用程序的不同阶段执行初始化和清理工作。

这里用到了 fx.Invoke 方法,顾名思义,就是调用某些方法,可以传入已有的方法名也可以使用匿名方法(不建议)。 fx 声明周期管理中 Hook 就是通过这个 API 实现的,案例如下:

package main  

import (  
    "context"  
    "go.uber.org/fx"    "go.uber.org/zap")  

func main() {  
    app := fx.New( //创建fx.App实例  
       fx.Provide(NewTester, func() *Age {  
          return &Age{Num: 18} //提供Age实例  
       }, func() *zap.Logger {  
          production, _ := zap.NewProduction() //提供zap.Logger实例  
          return production  
       }), //提供NewTester函数  
       fx.Invoke(register), //调用register函数  
    )  
    app.Run() //运行fx.App实例  
}  

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 register(lifecycle fx.Lifecycle, tester *Tester) {  
    lifecycle.Append(fx.Hook{  
       OnStart: func(context.Context) error {  
          tester.Log.Info("Starting server")  
          return nil  
       },  
       OnStop: func(context.Context) error {  
          tester.Log.Info("Stopping server")  
          return nil  
       },  
    })  
}

新增的 register 方法,通过 fx.Lifecycle 在应用程序启动和停止时执行一些自定义逻辑。具体的含义如下:

  1. register 函数
    • 这个函数接收两个参数:
      • lifecycle fx.Lifecycle:用于管理应用程序的生命周期。
      • tester *Tester:一个包含 zap.LoggerAge 的结构体实例。
  2. lifecycle.Append(fx.Hook{...})
    • lifecycle.Append 方法用于向应用程序的生命周期中添加钩子。
    • fx.Hook 结构体包含两个回调函数:OnStartOnStop,分别在应用程序启动和停止时调用。
  3. OnStart 函数
    • OnStart: func(context.Context) error { ... }
      • 这是一个在应用程序启动时执行的回调函数。
      • 这个函数记录一条日志信息 "Starting server",表示服务器正在启动。
      • 函数返回 nil,表示启动过程中没有发生错误。
  4. OnStop 函数
    • OnStop: func(context.Context) error { ... }
      • 这是一个在应用程序停止时执行的回调函数。
      • 这个函数记录一条日志信息 "Stopping server",表示服务器正在停止。
      • 函数返回 nil,表示停止过程中没有发生错误。

UberFx 框架中,fx.Hookfx.Lifecycle 通常一起使用,用于管理应用程序的生命周期和执行特定的初始化或清理逻辑。下面分别介绍它们的使用场景:

fx.Lifecycle 的使用场景

  1. 管理资源生命周期
    • 数据库连接:在应用程序启动时建立数据库连接,在停止时关闭连接。
    • 缓存初始化:在应用程序启动时加载和初始化缓存,在停止时清理缓存。
    • 消息队列连接:在应用程序启动时连接消息队列,在停止时断开连接。
  2. 服务启动和停止
    • Web 服务器:在应用程序启动时启动 Web 服务器,在停止时优雅地关闭服务器。
    • 定时任务:在应用程序启动时启动定时任务,在停止时停止定时任务。
  3. 日志记录和监控
    • 在应用程序的不同阶段记录日志,如 "应用启动" 和 "应用停止"。
    • 在应用程序启动和停止时发送监控指标,如 CPU 使用率、内存使用等。

fx.Hook 的使用场景

  1. 自定义初始化和清理逻辑
    • 启动时
      • 初始化数据库:在应用程序启动时初始化数据库连接池。
      • 加载配置:读取和加载应用程序的配置文件。
      • 注册 HTTP 路由:在应用程序启动时注册各种 HTTP 路由和中间件。
    • 停止时
      • 关闭数据库连接:优雅地关闭数据库连接。
      • 清理资源:释放所有的资源,确保应用程序停止时不留下任何未处理的事务。
  2. 启动和停止通知
    • 在应用程序启动时发送通知,如通过邮件或消息队列通知团队。
    • 在应用程序停止时执行最后的清理工作,并发送应用程序关闭通知。
  3. 调试和审计
    • 记录应用程序启动和停止时的调试信息,帮助排查问题。
    • 在停止时记录审计日志,如记录哪些资源被关闭或清理了。

相信通过基础的学习,已经对 fx 有了了解,并且可以着手构建测试项目了。下期我们继续分享 fx 上手进阶内容。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册