imtoken钱包下载 推荐|go fx
深入解析go依赖注入库go.uber.org/fx - 知乎
深入解析go依赖注入库go.uber.org/fx - 知乎首发于go 开源框架源码解析切换模式写文章登录/注册深入解析go依赖注入库go.uber.org/fx杨桃不爱程序员游戏行业服务器go 语言开发后面更新采用肝一篇go官方源码,肝一篇框架源码形式,伤肝->护肝,如果你喜欢就点个赞吧。官方源码比较伤肝(* ̄︶ ̄)。1依赖注入初识依赖注入来自开源项目Grafana 的源码,该项目框架采用依赖注入方式对各结构体字段进行赋值。DI 依赖注入包为https://github.com/facebookarchive/inject,后面我会专门介绍这个包依赖注入的原理。不过今天的主角是它:https://github.com/uber-go/fx。该包统一采用构造函数Newxx()形式进行依赖注入,对比与inject ,我认为比较好的点:采用Newxx()形式显示声明,更利于构造单元测试采用Newxx()能更直观,表明我这个对象需要什么,inject 后面是tag,与结构体字段混在一起。有时我们需要另一个对象,但不希望它出现在结构体字段里面来看看我们自己给结构体赋值怎么做假设我们一个对象需要b对象赋值,b 对象需要c 对象赋值,那么我们该这么写package main
type A struct {
B* B
}
func NewA( b *B)* A {
return &A{B: b}
}
type B struct {
C *C
}
func NewB(c * C)*B {
return &B{c}
}
type C struct {
}
func NewC()*C {
return &C{}
}
func main() {
//我们需要一个a
b:=NewB(NewC())
a:=NewA(b)
_=a
PrintA(a)
}
func PrintA(a* A) {
fmt.Println(*a)
}
如果选择依赖注入呢,实际情况可能更加复杂,如果有更好的方式,那么一定是DI 依赖注入了package main
import (
"fmt"
"go.uber.org/fx"
)
type A struct {
B* B
}
func NewA( b *B)* A {
return &A{B: b}
}
type B struct {
C *C
}
func NewB(c * C)*B {
return &B{c}
}
type C struct {
}
func NewC()*C {
return &C{}
}
func main() {
fx.New(
fx.Provide(NewB),
fx.Provide(NewA),
fx.Provide(NewC),
fx.Invoke(PrintA),
)
}
func PrintA(a* A) {
fmt.Println(*a)
}
文章末尾有完整http项目实践例子,附上github地址:https://github.com/yangtaolirong/fx-demo,大家可以根据自己需求进行优化。2使用New()该函数时创建一个依赖注入实例option 的结构// An Option configures an App using the functional options paradigm
// popularized by Rob Pike. If you're unfamiliar with this style, see
// https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html.
type Option interface {
fmt.Stringer
apply(*App)
}
option 必须使用下面的几个方法进行生成,来看个demopackage main
import (
"context"
"go.uber.org/fx"
)
type Girl struct {
Name string
Age int
}
func NewGirl()*Girl {
return &Girl{
Name: "苍井",
Age: 18,
}
}
type Gay struct {
Girl * Girl
}
func NewGay (girl * Girl)*Gay {
return &Gay{girl}
}
func main() {
app:=fx.New(
fx.Provide(NewGay),
fx.Provide(NewGirl),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
Provide()该函数将被依赖的对象的构造函数传进去,传进去的函数必须是个待返回值的函数指针fx.Provide(NewGay)fx.Provide(NewGirl)Invoke()该函数将函数依赖的对象作为参数传进函数然后调用函数func main() {
invoke:= func(gay* Gay) {
fmt.Println(gay.Girl) //&{苍井 18}
}
app:=fx.New(
fx.Provide(NewGay),
fx.Provide(NewGirl),
fx.Invoke(invoke),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
Supply()该函数直接提供被依赖的对象。不过这个supply 不能提供一个接口func main() {
invoke:= func(gay* Gay) {
fmt.Println(gay.Girl)
}
girl:=NewGirl() //直接提供对象
app:=fx.New(
fx.Provide(NewGay),
fx.Supply(girl),
fx.Invoke(invoke),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
不能提供接口类型,比如我们使用Provide可以提供一个SayInterface类型的接口,该代码运行不会报错,但我们换成supply 以后就会有问题type Girl struct {
Name string
Age int
}
func NewGirl()SayInterface {
return &Girl{
Name: "苍井",
Age: 18,
}
}
type Gay struct {
Girl * Girl
}
func (g* Girl)SayHello() {
fmt.Println("girl sayhello")
}
func NewGay (say SayInterface)*Gay {//此处能够正常获取到
return &Gay{}
}
type SayInterface interface {
SayHello()
}
func main() {
invoke:= func(gay *Gay) {
}
app:=fx.New(
fx.Provide(NewGirl),
fx.Provide(NewGay),
fx.Invoke(invoke),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
通过supply 提供就会报错func main() {
invoke:= func(gay *Gay) {
}
app:=fx.New(
fx.Supply(NewGirl()),
fx.Provide(NewGay),
fx.Invoke(invoke),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
或者这种形式func main() {
invoke:= func(gay *Gay) {
}
var girl SayInterface=&Girl{}
app:=fx.New(
fx.Supply(girl),
fx.Provide(NewGay),
fx.Invoke(invoke),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
错误:Failed: could not build arguments for function "main".main.func1 (D:/code/leetcode/fx.go:39): failed to build *
main.Gay: missing dependencies for function "main".NewGay (D:/code/leetcode/fx.go:29): missing type: main.SayIn
terface (did you mean *main.Girl?)
原因我会在后面分析,反正是识别成了结构体真正的类型而不是接口类型,平时在使用中,也是一个坑Populate()该函数将通过容器内值外面的变量进行赋值func main() {
invoke:= func(gay *Gay) {
}
var gay *Gay //定义一个对象,值为nil
app:=fx.New(
fx.Provide(NewGirl),
fx.Provide(NewGay),
fx.Invoke(invoke),
fx.Populate(&gay),//调用Populate,这里必须是指针,因为是通过*target 来给元素赋值的
)
fmt.Println(gay) //&{0xc00008c680},将NewGay返回的对象放进var定义的变量里面了
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
原理将传进来的参数,换成函数,参数为target,函数结果为类似下面这种类型,最后转换成invoke类型进行调用// Build a function that looks like:
//
// func(t1 T1, t2 T2, ...) {
// *targets[0] = t1
// *targets[1] = t2
// [...]
// }
//
下面是函数实现// Populate sets targets with values from the dependency injection container
// during application initialization. All targets must be pointers to the
// values that must be populated. Pointers to structs that embed In are
// supported, which can be used to populate multiple values in a struct.
//
// This is most helpful in unit tests: it lets tests leverage Fx's automatic
// constructor wiring to build a few structs, but then extract those structs
// for further testing.
func Populate(targets ...interface{}) Option {
// Validate all targets are non-nil pointers.
targetTypes := make([]reflect.Type, len(targets))
for i, t := range targets {
if t == nil {
return invokeErr(fmt.Errorf("failed to Populate: target %v is nil", i+1))
}
rt := reflect.TypeOf(t)
if rt.Kind() != reflect.Ptr {
return invokeErr(fmt.Errorf("failed to Populate: target %v is not a pointer type, got %T", i+1, t))
}
targetTypes[i] = reflect.TypeOf(t).Elem()
}
// Build a function that looks like:
//
// func(t1 T1, t2 T2, ...) {
// *targets[0] = t1
// *targets[1] = t2
// [...]
// }
//
fnType := reflect.FuncOf(targetTypes, nil, false /* variadic */) //制造函数的类型
fn := reflect.MakeFunc(fnType, func(args []reflect.Value) []reflect.Value {//制造函数
for i, arg := range args {
reflect.ValueOf(targets[i]).Elem().Set(arg)
}
return nil
})
return Invoke(fn.Interface()) //invoke选项
}
reflect.FuncOf该函数作用是通过指定的参数类型和返回类型创造一个函数,共有3个参数,variadic代表是不是可选参数FuncOf(in, out []Type, variadic bool) Typereflect.MakeFunc代表按照什么函数类型制造函数,其中第二个参数是个回调函数,代表函数的传参值和返回值,意思是将函数传进来的参数值赋值给Populate传进来的值Annotated http://fx.inannotated提供高级功能,让相同的对象按照tag能够赋值到一个结构体上面,结构体必须内嵌http://fx.intype Gay struct {
fx.In
Girl1 * Girl `name:"波多"`
Girl2 * Girl `name:"海翼"`
Girls []*Girl `group:"actor"`
}
func main() {
invoke:= func(gay Gay) {
fmt.Println(gay.Girl1.Name)//波多
fmt.Println(gay.Girl2.Name)//海翼
fmt.Println(len(gay.Girls),gay.Girls[0].Name)//1 杏梨
}
app:=fx.New(
fx.Invoke(invoke),
fx.Provide(
fx.Annotated{
Target: func() *Girl { return &Girl{Name: "波多"} },
Name: "波多",
},
fx.Annotated{
Target: func() *Girl { return &Girl{Name: "海翼"} },
Name: "海翼",
},
fx.Annotated{
Target: func() *Girl { return &Girl{Name: "杏梨"} },
Group: "actor",
},
),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
不带tag的annotated,下面这种写法是可以的type Gay struct {
fx.In
Girl1 * Girl
}
func main() {
invoke:= func(gay Gay) {
fmt.Println(gay.Girl1.Name)
}
app:=fx.New(
fx.Invoke(invoke),
fx.Provide(
fx.Annotated{
Target: func() *Girl { return &Girl{Name: "波多"} },
},
//下面不能再添加fx.Annotated,不能识别了,因为是匿名的
),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
group写多个,用","分开type Gay struct {
fx.In
Girl1[]* Girl `group:"actor"`
}
func main() {
invoke:= func(gay Gay) {
fmt.Println(len(gay.Girl1))
}
app:=fx.New(
fx.Invoke(invoke),
fx.Provide(
fx.Annotated{
Target: func() *Girl { return &Girl{Name: "波多"} },
Group: "actor,beauty",
},
),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
错误的写法,Group和Name 是不能同时存在的fx.Annotated{
Target: func() *Girl { return &Girl{Name: "波多"} },
Group: "actor,beauty",
Name:"波多"
},
当返回切片时,需要在group 后面加上flattenfunc NewGirl()[]*Girl {
return []*Girl{{
Name: "苍井",
Age: 18,
}}
}
type Gay struct {
fx.In
Girl1 []* Girl `group:"actor"`
}
func main() {
invoke:= func(gay Gay) {
fmt.Println(gay)
}
app:=fx.New(
fx.Invoke(invoke),
fx.Provide(
fx.Annotated{
Target: NewGirl,
Group: "actor,flatten",
},
),)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
fx.outfx.out会将当前结构体的字段按名字输出,相当于fx.Annotated{
Target: func() *Girl { return &Girl{Name: "海翼"} },
Name: "海翼",
},
//或者
fx.Annotated{
Target: func() *Girl { return &Girl{Name: "杏梨"} },
Group: "actor",
},
所以在另一个结构体写上http://fx.in 就能按名字接收到了type Gay struct {
fx.Out
Girl1 * Girl `name:"波多"`
}
type Gay1 struct {
fx.Out
Girl1 * Girl `name:"仓井"`
}
type Man struct {
fx.In
Girl1 * Girl `name:"波多"`
Girl2 * Girl `name:"仓井"`
}
func NewGay()Gay {
return Gay{
Girl1:&Girl{Name: "波多"},
}
}
func NewGay1()Gay1 {
return Gay1{
Girl1:&Girl{Name: "仓井"},
}
}
func main() {
invoke:= func(man Man) {
fmt.Println(man.Girl1.Name)//波多
fmt.Println(man.Girl2.Name) //仓井
}
app:=fx.New(
fx.Invoke(invoke),
fx.Provide(
NewGay,NewGay1,
),)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
源码解析核心方法New// New creates and initializes an App, immediately executing any functions
//创建和初始化app 实例,并且是立即执行注册和调用的
// registered via Invoke options. See the documentation of the App struct for
// details on the application's initialization, startup, and shutdown logic.
func New(opts ...Option) *App {
logger := fxlog.DefaultLogger(os.Stderr) //获取日志实例
app := &App{//创建app 实例
// We start with a logger that writes to stderr. One of the
// following three things can change this:
//
// - fx.Logger was provided to change the output stream
// - fx.WithLogger was provided to change the logger
// implementation
// - Both, fx.Logger and fx.WithLogger were provided
//
// The first two cases are straightforward: we use what the
// user gave us. For the last case, however, we need to fall
// back to what was provided to fx.Logger if fx.WithLogger
// fails.
log: logger,
startTimeout: DefaultTimeout, //启动超时时间
stopTimeout: DefaultTimeout, //停止超时时间
}
for _, opt := range opts {
opt.apply(app)//用opt 初始化app
}
// There are a few levels of wrapping on the lifecycle here. To quickly
// cover them:
//
// - lifecycleWrapper ensures that we don't unintentionally expose the
// Start and Stop methods of the internal lifecycle.Lifecycle type
// - lifecycleWrapper also adapts the internal lifecycle.Hook type into
// the public fx.Hook type.
// - appLogger ensures that the lifecycle always logs events to the
// "current" logger associated with the fx.App.
app.lifecycle = &lifecycleWrapper{ //初始生命周期函数
lifecycle.New(appLogger{app}),
}
var (
bufferLogger *logBuffer // nil if WithLogger was not used
// Logger we fall back to if the custom logger fails to build.
// This will be a DefaultLogger that writes to stderr if the
// user didn't use fx.Logger, and a DefaultLogger that writes
// to their output stream if they did.
fallbackLogger fxevent.Logger
)
if app.logConstructor != nil {
// Since user supplied a custom logger, use a buffered logger
// to hold all messages until user supplied logger is
// instantiated. Then we flush those messages after fully
// constructing the custom logger.
bufferLogger = new(logBuffer)
fallbackLogger, app.log = app.log, bufferLogger
}
app.container = dig.New( //创建container
dig.DeferAcyclicVerification(),
dig.DryRun(app.validate),
)
for _, p := range app.provides { //app.provides 通过opt 已经初始化了,所以这就是调用fx.Provide()里面的构造函数
app.provide(p)
}
frames := fxreflect.CallerStack(0, 0) // include New in the stack for default Provides
app.provide(provide{
Target: func() Lifecycle { return app.lifecycle }, //将app.lifecycle这个对象提供出去
Stack: frames,
})
//提供shutdowner,和dotGraph这两个实例
app.provide(provide{Target: app.shutdowner, Stack: frames})
app.provide(provide{Target: app.dotGraph, Stack: frames})
// If you are thinking about returning here after provides: do not (just yet)!
// If a custom logger was being used, we're still buffering messages.
// We'll want to flush them to the logger.
// If WithLogger and Printer are both provided, WithLogger takes
// precedence.
if app.logConstructor != nil {
// If we failed to build the provided logger, flush the buffer
// to the fallback logger instead.
if err := app.constructCustomLogger(bufferLogger); err != nil {
app.err = multierr.Append(app.err, err)
app.log = fallbackLogger
bufferLogger.Connect(fallbackLogger)
return app
}
}
// This error might have come from the provide loop above. We've
// already flushed to the custom logger, so we can return.
if app.err != nil {
return app
}
if err := app.executeInvokes(); err != nil { //执行调用
app.err = err
if dig.CanVisualizeError(err) {//如果错误可以可视化,就走下面逻辑打印
var b bytes.Buffer
dig.Visualize(app.container, &b, dig.VisualizeError(err))
err = errorWithGraph{
graph: b.String(),
err: err,
}
}
errorHandlerList(app.errorHooks).HandleError(err)
}
return app
}
下面将依次剖析这些方法Optionnew 函数传进来的Option结构,必须要实现对app 初始化的方法apply(*App),要实现打印接口fmt.Stringer方法,现在做框架传配置几乎都采用这种套路了, 优雅的传配置。// An Option configures an App using the functional options paradigm
// popularized by Rob Pike. If you're unfamiliar with this style, see
// https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html.
type Option interface {
fmt.Stringer
apply(*App)
}
app.providefunc (app *App) provide(p provide) {
if app.err != nil {
return
}
constructor := p.Target
if _, ok := constructor.(Option); ok {
app.err = fmt.Errorf("fx.Option should be passed to fx.New directly, "+
"not to fx.Provide: fx.Provide received %v from:\n%+v",
constructor, p.Stack)
return
}
var info dig.ProvideInfo
opts := []dig.ProvideOption{
dig.FillProvideInfo(&info),
}
defer func() {
var ev fxevent.Event
switch {
case p.IsSupply:
ev = &fxevent.Supplied{
TypeName: p.SupplyType.String(),
Err: app.err,
}
default:
outputNames := make([]string, len(info.Outputs))
for i, o := range info.Outputs {
outputNames[i] = o.String()
}
ev = &fxevent.Provided{
ConstructorName: fxreflect.FuncName(constructor),
OutputTypeNames: outputNames,
Err: app.err,
}
}
app.log.LogEvent(ev)
}()
//处理anotated类型,生成相应的选项opts
if ann, ok := constructor.(Annotated); ok {
switch {
case len(ann.Group) > 0 && len(ann.Name) > 0:
app.err = fmt.Errorf(
"fx.Annotated may specify only one of Name or Group: received %v from:\n%+v",
ann, p.Stack)
return
case len(ann.Name) > 0:
opts = append(opts, dig.Name(ann.Name))
case len(ann.Group) > 0:
opts = append(opts, dig.Group(ann.Group))
}
if err := app.container.Provide(ann.Target, opts...); err != nil {
app.err = fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", ann, p.Stack, err)
}
return
}
if reflect.TypeOf(constructor).Kind() == reflect.Func {
ft := reflect.ValueOf(constructor).Type()
for i := 0; i < ft.NumOut(); i++ {
t := ft.Out(i)
if t == reflect.TypeOf(Annotated{}) {
app.err = fmt.Errorf(
"fx.Annotated should be passed to fx.Provide directly, "+
"it should not be returned by the constructor: "+
"fx.Provide received %v from:\n%+v",
fxreflect.FuncName(constructor), p.Stack)
return
}
}
}
if err := app.container.Provide(constructor, opts...); err != nil {
app.err = fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", fxreflect.FuncName(constructor), p.Stack, err)
}
}
app.executeInvokes该函数将会执行函数调用,fx.Inovke()添加invoke 函数调用// Execute invokes in order supplied to New, returning the first error
// encountered.
func (app *App) executeInvokes() error {
// TODO: consider taking a context to limit the time spent running invocations.
for _, i := range app.invokes { //循环遍历invokes函数
if err := app.executeInvoke(i); err != nil {
return err
}
}
return nil
}
app.executeInvoke//执行调用
func (app *App) executeInvoke(i invoke) (err error) {
fn := i.Target
fnName := fxreflect.FuncName(fn) //获取调用的函数名
//日志相关
app.log.LogEvent(&fxevent.Invoking{FunctionName: fnName})
defer func() {
app.log.LogEvent(&fxevent.Invoked{
FunctionName: fnName,
Err: err,
Trace: fmt.Sprintf("%+v", i.Stack), // format stack trace as multi-line
})
}()
//对fn 进行校验,如果还是Option类型,说明是错误了,报错
if _, ok := fn.(Option); ok {
return fmt.Errorf("fx.Option should be passed to fx.New directly, "+
"not to fx.Invoke: fx.Invoke received %v from:\n%+v",
fn, i.Stack)
}
return app.container.Invoke(fn) //执行容器的调用方法Invoke
}
dig.Containercontainer.Provide该函数作用是将构造函数赋值给容器,在这之前还要做一系列检查// Provide teaches the container how to build values of one or more types and
// expresses their dependencies.
//
// The first argument of Provide is a function that accepts zero or more
// parameters and returns one or more results. The function may optionally
// return an error to indicate that it failed to build the value. This
// function will be treated as the constructor for all the types it returns.
// This function will be called AT MOST ONCE when a type produced by it, or a
// type that consumes this function's output, is requested via Invoke. If the
// same types are requested multiple times, the previously produced value will
// be reused.
//
// In addition to accepting constructors that accept dependencies as separate
// arguments and produce results as separate return values, Provide also
// accepts constructors that specify dependencies as dig.In structs and/or
// specify results as dig.Out structs.
func (c *Container) Provide(constructor interface{}, opts ...ProvideOption) error {
ctype := reflect.TypeOf(constructor)
if ctype == nil { //构造函数不能为nil
return errors.New("can't provide an untyped nil")
}
if ctype.Kind() != reflect.Func { //构造函数必须是函数
return errf("must provide constructor function, got %v (type %v)", constructor, ctype)
}
var options provideOptions
for _, o := range opts {
o.applyProvideOption(&options) //如果有选项就应用选项
}
if err := options.Validate(); err != nil {
return err
}
//调用provide
if err := c.provide(constructor, options); err != nil {
return errProvide{
Func: digreflect.InspectFunc(constructor),
Reason: err,
}
}
return nil
}
providefunc (c *Container) provide(ctor interface{}, opts provideOptions) error {
n, err := newNode(
ctor,
nodeOptions{
ResultName: opts.Name,
ResultGroup: opts.Group,
},
) //创建1个node节点
if err != nil {
return err
}
//验证结果
keys, err := c.findAndValidateResults(n)
if err != nil {
return err
}
ctype := reflect.TypeOf(ctor) //获取构造函数的反射类型
if len(keys) == 0 {
return errf("%v must provide at least one non-error type", ctype)
}
for k := range keys {
c.isVerifiedAcyclic = false
oldProviders := c.providers[k]
c.providers[k] = append(c.providers[k], n) //给c.providers[k] 赋值,代表该key 哪些节点能够提供
if c.deferAcyclicVerification {
continue
}
//验证是否循环依赖
if err := verifyAcyclic(c, n, k); err != nil {
c.providers[k] = oldProviders
return err
}
c.isVerifiedAcyclic = true
}
c.nodes = append(c.nodes, n)
// Record introspection info for caller if Info option is specified
if info := opts.Info; info != nil { //一些打印信息
params := n.ParamList().DotParam()
results := n.ResultList().DotResult()
info.ID = (ID)(n.id)
info.Inputs = make([]*Input, len(params))
info.Outputs = make([]*Output, len(results))
for i, param := range params {
info.Inputs[i] = &Input{
t: param.Type,
optional: param.Optional,
name: param.Name,
group: param.Group,
}
}
for i, res := range results {
info.Outputs[i] = &Output{
t: res.Type,
name: res.Name,
group: res.Group,
}
}
}
return nil
}
该步主要是生成node节点newNodefunc newNode(ctor interface{}, opts nodeOptions) (*node, error) {
cval := reflect.ValueOf(ctor) //获取构造函数的反射值
ctype := cval.Type()//获取构造函数的反射类型,获取构造函数的指针
cptr := cval.Pointer()
params, err := newParamList(ctype)//获取参数列表
if err != nil {
return nil, err
}
results, err := newResultList(//获取返回列表
ctype,
resultOptions{
Name: opts.ResultName,
Group: opts.ResultGroup,
},
)
if err != nil {
return nil, err
}
return &node{
ctor: ctor,//构造函数
ctype: ctype, //构造函数类型
location: digreflect.InspectFunc(ctor),
id: dot.CtorID(cptr), //用指针地址作为节点的id
paramList: params,//构造函数的参数
resultList: results,//构造函数的结果
}, err
}
newParamList// newParamList builds a paramList from the provided constructor type.
//
// Variadic arguments of a constructor are ignored and not included as
// dependencies.
func newParamList(ctype reflect.Type) (paramList, error) {
numArgs := ctype.NumIn() //获取invoke 函数的参数
if ctype.IsVariadic() { //如果函数是可选参数,我们跳过最后一个参数,从这里可以知道,invoke 函数后面写可选参数,是可以的
// NOTE: If the function is variadic, we skip the last argument
// because we're not filling variadic arguments yet. See #120.
numArgs--
}
pl := paramList{
ctype: ctype,
Params: make([]param, 0, numArgs),
}
for i := 0; i < numArgs; i++ {
p, err := newParam(ctype.In(i)) //获取函数的参数列表
if err != nil {
return pl, errf("bad argument %d", i+1, err)
}
pl.Params = append(pl.Params, p) //添加封装后的参数
}
return pl, nil
}
newParam// newParam builds a param from the given type. If the provided type is a
// dig.In struct, an paramObject will be returned.
func newParam(t reflect.Type) (param, error) {
switch {
//参数如果是out 类型则报错
case IsOut(t) || (t.Kind() == reflect.Ptr && IsOut(t.Elem())) || embedsType(t, _outPtrType):
return nil, errf("cannot depend on result objects", "%v embeds a dig.Out", t)
case IsIn(t)://如果是fx.In 类型,创建newParamObject类型
return newParamObject(t)
case embedsType(t, _inPtrType):
return nil, errf(
"cannot build a parameter object by embedding *dig.In, embed dig.In instead",
"%v embeds *dig.In", t)
case t.Kind() == reflect.Ptr && IsIn(t.Elem()):
return nil, errf(
"cannot depend on a pointer to a parameter object, use a value instead",
"%v is a pointer to a struct that embeds dig.In", t)
default:
//创建paramSingle类型
return paramSingle{Type: t}, nil
}
}
newResultListfunc newResultList(ctype reflect.Type, opts resultOptions) (resultList, error) {
rl := resultList{
ctype: ctype,
Results: make([]result, 0, ctype.NumOut()),
resultIndexes: make([]int, ctype.NumOut()),
}
resultIdx := 0
for i := 0; i < ctype.NumOut(); i++ { //循环遍历构造函数的输出参数
t := ctype.Out(i)//获取参数
if isError(t) {//如果是错误类型,将这行结果索引赋值为-1
rl.resultIndexes[i] = -1
continue
}
r, err := newResult(t, opts)
if err != nil {
return rl, errf("bad result %d", i+1, err)
}
rl.Results = append(rl.Results, r)
rl.resultIndexes[i] = resultIdx //添加结果类型,注意这里没有用i,说明是有效的返回类型才会添加
resultIdx++
}
return rl, nil
}
newResult// newResult builds a result from the given type.
func newResult(t reflect.Type, opts resultOptions) (result, error) {
switch {
//如果该类型内嵌fx.IN,那么就报错
case IsIn(t) || (t.Kind() == reflect.Ptr && IsIn(t.Elem())) || embedsType(t, _inPtrType):
return nil, errf("cannot provide parameter objects", "%v embeds a dig.In", t)
//是错误也返回,不能返回错误类型在构造函数里面
case isError(t):
return nil, errf("cannot return an error here, return it from the constructor instead")
//结构体如果内嵌fx.Out,返回ResultObject类型
case IsOut(t):
return newResultObject(t, opts)
//结果类型内嵌必须是dig.Out而不是*dig.Out
case embedsType(t, _outPtrType):
return nil, errf(
"cannot build a result object by embedding *dig.Out, embed dig.Out instead",
"%v embeds *dig.Out", t)
//结果对象不能是指针
case t.Kind() == reflect.Ptr && IsOut(t.Elem()):
return nil, errf(
"cannot return a pointer to a result object, use a value instead",
"%v is a pointer to a struct that embeds dig.Out", t)
case len(opts.Group) > 0: //如果构造函数是group类型,则创建resultGrouped类型
g, err := parseGroupString(opts.Group)
if err != nil {
return nil, errf(
"cannot parse group %q", opts.Group, err)
}
rg := resultGrouped{Type: t, Group: g.Name, Flatten: g.Flatten}
if g.Flatten { //如果group 后面有g.Flatten,那么这个构造函数返回值必须是切片类型
if t.Kind() != reflect.Slice {
return nil, errf(
"flatten can be applied to slices only",
"%v is not a slice", t)
}
rg.Type = rg.Type.Elem()
}
return rg, nil
default:
//返回单个参数类型
return resultSingle{Type: t, Name: opts.Name}, nil
}
}
根据构造函数返回的每个参数类型和选项创建一个result对象可见内嵌fx.Out 返回必须是个对象findAndValidateResults// Builds a collection of all result types produced by this node.
func (c *Container) findAndValidateResults(n *node) (map[key]struct{}, error) {
var err error
keyPaths := make(map[key]string)
walkResult(n.ResultList(), connectionVisitor{
c: c,
n: n,
err: &err,
keyPaths: keyPaths,
})
if err != nil {
return nil, err
}
keys := make(map[key]struct{}, len(keyPaths))
for k := range keyPaths {
keys[k] = struct{}{}
}
return keys, nil
}
walkResult// walkResult walks the result tree for the given result with the provided
// visitor.
//
// resultVisitor.Visit will be called on the provided result and if a non-nil
// resultVisitor is received, it will be used to walk its descendants. If a
// resultObject or resultList was visited, AnnotateWithField and
// AnnotateWithPosition respectively will be called before visiting the
// descendants of that resultObject/resultList.
//
// This is very similar to how go/ast.Walk works.
func walkResult(r result, v resultVisitor) {
v = v.Visit(r)
if v == nil {
return
}
switch res := r.(type) {
case resultSingle, resultGrouped:
// No sub-results
case resultObject:
w := v
for _, f := range res.Fields {
if v := w.AnnotateWithField(f); v != nil {
walkResult(f.Result, v)//递归调用walkResult,传入参数为返回结构体的字段
}
}
case resultList:
w := v
for i, r := range res.Results {
if v := w.AnnotateWithPosition(i); v != nil {
walkResult(r, v)//递归调用walkResult,传入参数为切片的每个值
}
}
default:
panic(fmt.Sprintf(
"It looks like you have found a bug in dig. "+
"Please file an issue at https://github.com/uber-go/dig/issues/ "+
"and provide the following message: "+
"received unknown result type %T", res))
}
}
connectionVisitor.Visitfunc (cv connectionVisitor) Visit(res result) resultVisitor {
// Already failed. Stop looking.
if *cv.err != nil {
return nil
}
path := strings.Join(cv.currentResultPath, ".")
switch r := res.(type) {
case resultSingle:
k := key{name: r.Name, t: r.Type}
//如果k 存在,并且返回值类型是resultSingle类型,说明该提供依赖以及存在了,根name和group 稍微有区别
if conflict, ok := cv.keyPaths[k]; ok {
*cv.err = errf(
"cannot provide %v from %v", k, path,
"already provided by %v", conflict,
)
return nil
}
if ps := cv.c.providers[k]; len(ps) > 0 {
cons := make([]string, len(ps))
for i, p := range ps {
cons[i] = fmt.Sprint(p.Location())
}
*cv.err = errf(
"cannot provide %v from %v", k, path,
"already provided by %v", strings.Join(cons, "; "),
)
return nil
}
cv.keyPaths[k] = path
case resultGrouped:
// we don't really care about the path for this since conflicts are
// okay for group results. We'll track it for the sake of having a
// value there.
//group 类型直接赋值就行了,代表该类型提供了值
k := key{group: r.Group, t: r.Type}
cv.keyPaths[k] = path
}
return cv
}
container.InvokeInvoke会在初始化依赖后调用,这个函数的任何参数都会被认为是它的依赖,这个依赖被初始化没有顺序,invoke 调用可能会有错误,这个错误将会被返回// Invoke runs the given function after instantiating its dependencies.
//
// Any arguments that the function has are treated as its dependencies. The
// dependencies are instantiated in an unspecified order along with any
// dependencies that they might have.
//
// The function may return an error to indicate failure. The error will be
// returned to the caller as-is.
func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error {
ftype := reflect.TypeOf(function) //获取函数类型
if ftype == nil { //判断是不是nil
return errors.New("can't invoke an untyped nil")
}
if ftype.Kind() != reflect.Func { //判断是不是函数
return errf("can't invoke non-function %v (type %v)", function, ftype)
}
pl, err := newParamList(ftype)//获取函数参数列表
if err != nil {
return err
}
if err := shallowCheckDependencies(c, pl); err != nil { //检查依赖
return errMissingDependencies{
Func: digreflect.InspectFunc(function),
Reason: err,
}
}
if !c.isVerifiedAcyclic {//没有验证循环,验证循环
if err := c.verifyAcyclic(); err != nil {
return err
}
}
args, err := pl.BuildList(c)//将参数赋值,返回赋值后的参数
if err != nil {
return errArgumentsFailed{
Func: digreflect.InspectFunc(function),
Reason: err,
}
}
returned := c.invokerFn(reflect.ValueOf(function), args)//调用函数结果
if len(returned) == 0 {
return nil
}
if last := returned[len(returned)-1]; isError(last.Type()) {//如果最后一个结果是错误,会将此错误进行返回
if err, _ := last.Interface().(error); err != nil {
return err
}
}
return nil
}
shallowCheckDependencies检查依赖是否缺少,比如func( a A),如果A 这种类型的对象在container 里面找不到,也就是说构造函数没有提供,那么在这里将会报错
// Checks that all direct dependencies of the provided param are present in
// the container. Returns an error if not.
func shallowCheckDependencies(c containerStore, p param) error {
var err errMissingTypes
var addMissingNodes []*dot.Param
walkParam(p, paramVisitorFunc(func(p param) bool {
ps, ok := p.(paramSingle)
if !ok {
return true
}
if ns := c.getValueProviders(ps.Name, ps.Type); len(ns) == 0 && !ps.Optional {
err = append(err, newErrMissingTypes(c, key{name: ps.Name, t: ps.Type})...)
addMissingNodes = append(addMissingNodes, ps.DotParam()...)
}
return true
}))
if len(err) > 0 {
return err
}
return nil
}
verifyAcyclicif !c.isVerifiedAcyclic {
if err := c.verifyAcyclic(); err != nil {
return err
}
}
校验循环,如果没有校验过循环,就校验循环func (c *Container) verifyAcyclic() error {
visited := make(map[key]struct{})
for _, n := range c.nodes {
if err := detectCycles(n, c, nil /* path */, visited); err != nil {
return errf("cycle detected in dependency graph", err)
}
}
c.isVerifiedAcyclic = true
return nil
}
检验循环的原理是递归遍历该参数的提供者,如果该提供者出现过说明出现了循环,例如a ->b->c ->d->a ,d 的提供者是a ,但a 已经出现过了,所以出现了循环pl.BuildList该函数通过容器,查找到invoke 函数需要的参数值,然后通过下面的invokerFn进行调用。该函数返回有序的结果列表// BuildList returns an ordered list of values which may be passed directly
// to the underlying constructor.
func (pl paramList) BuildList(c containerStore) ([]reflect.Value, error) {
args := make([]reflect.Value, len(pl.Params))
for i, p := range pl.Params {
var err error
args[i], err = p.Build(c)
if err != nil {
return nil, err
}
}
return args, nil
}
对象不用的参数p,Build 表现不也一样,这里以paramSingle为例func (ps paramSingle) Build(c containerStore) (reflect.Value, error) {
if v, ok := c.getValue(ps.Name, ps.Type); ok { //从容器里面查找该名字和类型的参数,如果查到了就返回
return v, nil
}
//如果上面一步没有直接获取到,那么就查找能提供这个key 的节点,ps.Name, ps.Type组合成的结构体为key
providers := c.getValueProviders(ps.Name, ps.Type)
if len(providers) == 0 { //如果提供的节点找不到,如果参数是可选的,那么直接返回零值
if ps.Optional {
return reflect.Zero(ps.Type), nil
}
//如果没找到说明没有这个类型直接返回
return _noValue, newErrMissingTypes(c, key{name: ps.Name, t: ps.Type})
}
for _, n := range providers {
err := n.Call(c)
if err == nil {
continue
}
// If we're missing dependencies but the parameter itself is optional,
// we can just move on.
if _, ok := err.(errMissingDependencies); ok && ps.Optional {
return reflect.Zero(ps.Type), nil
}
return _noValue, errParamSingleFailed{
CtorID: n.ID(),
Key: key{t: ps.Type, name: ps.Name},
Reason: err,
}
}
// If we get here, it's impossible for the value to be absent from the
// container.
v, _ := c.getValue(ps.Name, ps.Type) //再尝试找,如果查找到这里,那么一定是有值得
return v, nil
}
Callcall 调用节点的构造函数,获得结果,然后存储在该节点里面,这个过程可能会出现递归,比如a 依赖b,b 的参数依赖c,就会调用BuildList,一层一层往上找,找不到返回错误,找到了,最后调用a 的构造函数创建结果// Call calls this node's constructor if it hasn't already been called and
// injects any values produced by it into the provided container.
func (n *node) Call(c containerStore) error {
if n.called { //这里用来标识该节点是否被call 过
return nil
}
if err := shallowCheckDependencies(c, n.paramList); err != nil {
return errMissingDependencies{
Func: n.location,
Reason: err,
}
}
args, err := n.paramList.BuildList(c) //一个递归过程,将该节点的参数进行BuildList,获取该节点的参数
if err != nil {
return errArgumentsFailed{
Func: n.location,
Reason: err,
}
}
receiver := newStagingContainerWriter()
//然后调用该节点的构造函数,将刚刚获取的参数传进去进行调用,获取调用后的results
results := c.invoker()(reflect.ValueOf(n.ctor), args)
if err := n.resultList.ExtractList(receiver, results); err != nil {
return errConstructorFailed{Func: n.location, Reason: err}
}
receiver.Commit(c)//将结果放入container
n.called = true
return nil
}
ExtractList ExtractList 将构造函数获取的结果赋值到节点的Resultsfunc (rl resultList) ExtractList(cw containerWriter, values []reflect.Value) error {
for i, v := range values { //循环遍历结果值
if resultIdx := rl.resultIndexes[i]; resultIdx >= 0 {
rl.Results[resultIdx].Extract(cw, v) //将结果值赋值到containerWriter里面去
continue
}
//调用结构包含err,直接返回
if err, _ := v.Interface().(error); err != nil {
return err
}
}
return nil
}
Extract就是给containerWriter赋值,然后最后调用 receiver.Commit(c)将值复制到容器里面去func (rs resultSingle) Extract(cw containerWriter, v reflect.Value) {
cw.setValue(rs.Name, rs.Type, v)
}
invokerFn默认的调用,就是通过反射获取invoke 函数的// invokerFn specifies how the container calls user-supplied functions.
type invokerFn func(fn reflect.Value, args []reflect.Value) (results []reflect.Value)
func defaultInvoker(fn reflect.Value, args []reflect.Value) []reflect.Value {
return fn.Call(args)
}
项目实践下面我们将通过搭建一个http 服务器在项目中实践来练习fx 的使用,项目目录结构server/main.gopackage main
import (
"server/pkg/config"
"server/pkg/log"
"server/server"
)
func main() {
srv:=server.NewServer() //创建一个服务器
srv.Provide(
log.GetLogger, //依赖注入Logger
config.NewConfig,//依赖注入配置文件
)
srv.Run()//运行服务
}
server/pkg/router/router.gopackage router
import "gopkg.in/macaron.v1"
func Register(router * macaron.Router) {
router.Get("/hello", func(ctx *macaron.Context) {
ctx.Write([]byte("hello"))
})
}
server/http_server.gopackage server
import (
"fmt"
"go.uber.org/zap"
"gopkg.in/macaron.v1"
"net/http"
"server/pkg/config"
)
type HttpServer struct {
cfg * config.Config
logger *zap.Logger
mar * macaron.Macaron
}
func NewHttpServer(cfg * config.Config,logger *zap.Logger)*HttpServer {
return &HttpServer{
cfg: cfg,
logger: logger.Named("http_server"),
mar:macaron.Classic() ,
}
}
func (srv* HttpServer)Run()error {
router.Register(srv.mar.Router)
addr:=fmt.Sprintf("0.0.0.0:%v",srv.cfg.HttpConfig.Port)
srv.logger.Info("http run ",zap.String("addr",addr))
return http.ListenAndServe(addr, srv.mar)
}
server/server.gopackage server
import (
"go.uber.org/fx"
"golang.org/x/sync/errgroup"
)
type Server struct {
group errgroup.Group //errgroup,参考我的文章,专门讲这个原理
app *fx.App //fx 实例
provides []interface{}
invokes []interface{}
supplys []interface{}
httSrv *HttpServer //该http server 可以换成fibber gin 之类的
}
func NewServer(
)*Server {
return &Server{
}
}
func(srv*Server) Run() {
srv.app=fx.New(
fx.Provide(srv.provides...),
fx.Invoke(srv.invokes...),
fx.Supply(srv.supplys...),
fx.Provide(NewHttpServer),//注入http server
fx.Supply(srv),
fx.Populate(&srv.httSrv), //给srv 实例赋值
fx.NopLogger,//禁用fx 默认logger
)
srv.group.Go(srv.httSrv.Run) //启动http 服务器
err:=srv.group.Wait() //等待子协程退出
if err!=nil{
panic(err)
}
}
func(srv*Server)Provide(ctr ...interface{}){
srv.provides= append(srv.provides, ctr...)
}
func(srv*Server)Invoke(invokes ...interface{}){
srv.invokes=append(srv.invokes,invokes...)
}
func(srv*Server)Supply(objs ...interface{}){
srv.supplys=append(srv.supplys,objs...)
}
server/pkg/config/config.gopackage config
import (
"gopkg.in/yaml.v2"
"io/ioutil"
)
type Config struct {
HttpConfig struct{
Port int `yaml:"port"`
} `yaml:"http"`
LogConfig struct{
Output string`yaml:"output"`
} `yaml:"log"`
}
func NewConfig()*Config {
data,err:=ioutil.ReadFile("./config.yaml")
if err!=nil{
panic(err)
}
c:=&Config{}
err=yaml.Unmarshal(data,c)
if err!=nil{
panic(err)
}
return c
}
server/pkg/log/log.gopackage log
import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"server/pkg/config"
)
func GetLogger(cfg *config.Config)*zap.Logger {
writeSyncer := getLogWriter()
encoder := getEncoder()
switch cfg.LogConfig.Output {
case "all":
writeSyncer=zapcore.NewMultiWriteSyncer(os.Stdout,writeSyncer) //暂时不启用文件
case "file":
default:
writeSyncer=zapcore.NewMultiWriteSyncer(os.Stdout) //暂时不启用文件
}
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
logger := zap.New(core, zap.AddCaller())
return logger
}
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./data/server.log",
MaxSize: 1,
MaxBackups: 5,
MaxAge: 30,
Compress: false,
}
return zapcore.AddSync(lumberJackLogger)
}
server/config.yamlhttp:
port: 9999
log:
#console:终端,file:文件,all:所有
output: "console"启动服务器:go run main.go
01T19:51:55.169+0800 INFO http_server server/http_server.go:28 http run {"addr": "0.0.0.0:9999"}浏览器输入http://127.0.0.1:9999/hello,可以看见打印hello编辑于 2021-10-24 23:05Go 语言Go 编程赞同 247 条评论分享喜欢收藏申请转载文章被以下专栏收录go 开源框架源码解析go 各种优秀开源框架源
解析 Golang 依赖注入经典解决方案 uber/fx 理论篇 - 掘金
解析 Golang 依赖注入经典解决方案 uber/fx 理论篇 - 掘金
首页 首页
沸点
课程
直播
活动
竞赛
商城
APP
插件 搜索历史
清空
创作者中心
写文章 发沸点 写笔记 写代码 草稿箱 创作灵感
查看更多
会员
登录
注册
解析 Golang 依赖注入经典解决方案 uber/fx 理论篇
ag9920
2022-10-12
1,720
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情
开篇
今天继续我们的【依赖注入开源解决方案系列】, Dependency Injection 业界的开源库非常多,大家可以凭借自己的喜好也业务的复杂度来选型。基于 github star 数量以及方案的全面性,易用性上。推荐这两个:
1.【代码生成】派系推荐大家用 wire, 做的事情非常轻量级,省下大家手写代码的负担,没有太多 DI 工具带来的结构性改造;
2.【反射】派系推荐大家用 uber/fx,功能非常强大,很全面,也比较符合直觉。
二者都需要显式声明依赖,这一点对程序的可读性是好事,两个库的 star 也都非常多。建议大家有兴趣的话研读一下。不管是 codegen 还是 reflect(结合 interface{},泛型)都是 Golang 学习体系中必须的能力,否则很难实现通用的一些能力。
今天我们来看看 uber/fx 这个反射派系的经典之作,这是 uber 家基于 dig 的又一步进化。
uber/fx
Fx is a dependency injection system for Go.
fx 是 uber 2017 年开源的依赖注入解决方案,不仅仅支持常规的依赖注入,还支持生命周期管理。
从官方的视角看,fx 能为开发者提供的三大优势:
代码复用:方便开发者构建松耦合,可复用的组件;
消除全局状态:Fx 会帮我们维护好单例,无需借用 init() 函数或者全局变量来做这件事了;
经过多年 Uber 内部验证,足够可信。
我们从 uber-go/fx 看到的是 v1 的版本,fx 是遵循 SemVer 规范的,保障了兼容性,这一点大家可以放心。
从劣势的角度分析,其实 uber/fx 最大的劣势还是大量使用反射,导致项目启动阶段会需要一些性能消耗,但这一般是可以接受的。如果对性能有高要求,建议还是采取 wire 这类 codegen 的依赖注入解法。
目前市面上对 Fx 的介绍文章并不多,笔者在学习的时候也啃了很长时间官方文档,这一点有好有坏。的确,再多的例子,再多的介绍,也不如一份完善的官方文档更有力。但同时也给初学者带来较高的门槛。
今天这篇文章希望从一个开发者的角度,带大家理解 Fx 如何使用。
添加 fx 的依赖需要用下面的命令:
go get go.uber.org/fx@v1
后面我们会有专门的一篇文章,拿一个实战项目来给大家展示,如何使用 Fx,大家同时也可以参考官方 README 中的 Getting Started 来熟悉。
下面一步一步来,我们先来看看 uber/fx 中的核心概念。
provider 声明依赖关系
在我们的业务服务的声明周期中,对于各个 module 的初始化应该基于我们的 dependency graph 来合理进行。先初始化无外部依赖的对象,随后基于这些对象,初始化对它们有依赖的对象。
Provider 就是我们常说的构造器,能够提供对象的生成逻辑。在 Fx 启动时会创建一个容器,我们需要将业务的构造器传进来,作为 Provider。类似下面这样:
app = fx.New(
fx.Provide(newZapLogger),
fx.Provide(newRedisClient),
fx.Provide(newMeaningOfLifeCacheRedis),
fx.Provide(newMeaningOfLifeHandler),
)
这里面的 newXXX 函数,就是我们的构造器,类似这样:
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
我们只需要通过 fx.Provide 方法传入进容器,就完成了将对象提供出去的使命。随后 fx 会在需要的时候调用我们的 Provider,生成单例对象使用。
当然,构造器不光是这种没有入参的。还有一些对象是需要显式的传入依赖:
func NewHandler(logger *log.Logger) (http.Handler, error) {
logger.Print("Executing NewHandler.")
return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
logger.Print("Got a request.")
}), nil
}
注意,这里返回的 http.Handler 也可以成为别人的依赖。
这些,我们通通不用关心!
fx 会自己通过反射,搞明白哪个 Provider 需要什么,能提供什么。构建出来整个 dependency graph。
// Provide registers any number of constructor functions, teaching the
// application how to instantiate various types. The supplied constructor
// function(s) may depend on other types available in the application, must
// return one or more objects, and may return an error. For example:
//
// // Constructs type *C, depends on *A and *B.
// func(*A, *B) *C
//
// // Constructs type *C, depends on *A and *B, and indicates failure by
// // returning an error.
// func(*A, *B) (*C, error)
//
// // Constructs types *B and *C, depends on *A, and can fail.
// func(*A) (*B, *C, error)
//
// The order in which constructors are provided doesn't matter, and passing
// multiple Provide options appends to the application's collection of
// constructors. Constructors are called only if one or more of their returned
// types are needed, and their results are cached for reuse (so instances of a
// type are effectively singletons within an application). Taken together,
// these properties make it perfectly reasonable to Provide a large number of
// constructors even if only a fraction of them are used.
//
// See the documentation of the In and Out types for advanced features,
// including optional parameters and named instances.
//
// Constructor functions should perform as little external interaction as
// possible, and should avoid spawning goroutines. Things like server listen
// loops, background timer loops, and background processing goroutines should
// instead be managed using Lifecycle callbacks.
func Provide(constructors ...interface{}) Option {
return provideOption{
Targets: constructors,
Stack: fxreflect.CallerStack(1, 0),
}
}
作为开发者,我们只需要保证,所有我们需要的依赖,都通过 fx.Provide 函数提供即可。另外需要注意,虽然上面我们是每个 fx.Provide,都只包含一个构造器,实际上他是支持多个构造器的。
module 模块化组织依赖
// Module is a named group of zero or more fx.Options.
// A Module creates a scope in which certain operations are taken
// place. For more information, see [Decorate], [Replace], or [Invoke].
func Module(name string, opts ...Option) Option {
mo := moduleOption{
name: name,
options: opts,
}
return mo
}
fx 中的 module 也是经典的概念。实际上我们在进行软件开发时,分层分包是不可避免的。而 fx 也是基于模块化编程。使用 module 能够帮助我们更方便的管理依赖:
// ProvideLogger to fx
func ProvideLogger() *zap.SugaredLogger {
logger, _ := zap.NewProduction()
slogger := logger.Sugar()
return slogger
}
// Module provided to fx
var Module = fx.Options(
fx.Provide(ProvideLogger),
)
我们的 Module 是一个可导出的变量,包含了一组 fx.Option,这里包含了各个 Provider。
这样,我们就不必要在容器初始化时传入那么多 Provider 了,而是每个 Module 干好自己的事即可。
func main() {
fx.New(
fx.Provide(http.NewServeMux),
fx.Invoke(server.New),
fx.Invoke(registerHooks),
loggerfx.Module,
).Run()
}
lifecycle 给应用生命周期加上钩子
// Lifecycle allows constructors to register callbacks that are executed on
// application start and stop. See the documentation for App for details on Fx
// applications' initialization, startup, and shutdown logic.
type Lifecycle interface {
Append(Hook)
}
// A Hook is a pair of start and stop callbacks, either of which can be nil.
// If a Hook's OnStart callback isn't executed (because a previous OnStart
// failure short-circuited application startup), its OnStop callback won't be
// executed.
type Hook struct {
OnStart func(context.Context) error
OnStop func(context.Context) error
}
lifecycle 是 Fx 定义的一个接口。我们可以对 fx.Lifecycle 进行 append 操作,增加钩子函数,这里就可以支持我们订阅一些指定行为,如 OnStart 和 OnStop。
如果执行某个 OnStart 钩子时出现错误,应用会立刻停止后续的 OnStart,并针对此前已经执行过 OnStart 的钩子执行对应的 OnStop 用于清理资源。
这里 fx 加上了 15 秒的超时限制,通过 context.Context 实现,大家记得控制好自己的钩子函数执行时间。
invoker 应用的启动器
provider 是懒加载的,仅仅 Provide 出来我们的构造器,是不会当时就触发调用的,而 invoker 则能够直接触发业务提供的函数运行。并且支持传入一个 fx.Lifecycle 作为入参,业务可以在这里 append 自己想要的 hook。
假设我们有一个 http server,希望在 fx 应用启动的时候同步开启。这个时候就需要两个入参:
fx.Lifecycle
我们的主依赖(通常是对服务接口的实现,一个 handler)
我们将这里的逻辑封装起来,就可以作为一个 invoker 让 Fx 来调用了。看下示例代码:
func runHttpServer(lifecycle fx.Lifecycle, molHandler *MeaningOfLifeHandler) {
lifecycle.Append(fx.Hook{OnStart: func(context.Context) error {
r := fasthttprouter.New()
r.Handle(http.MethodGet, "/what-is-the-meaning-of-life", molHandler.Handle)
return fasthttp.ListenAndServe("localhost:8080", r.Handler)
}})
}
下面我们将它加入 Fx 容器初始化的流程中:
fx.New(
fx.Provide(newZapLogger),
fx.Provide(newRedisClient),
fx.Provide(newMeaningOfLifeCacheRedis),
fx.Provide(newMeaningOfLifeHandler),
fx.Invoke(runHttpServer),
)
这样在创建容器时,我们的 runHttpServer 就会被调用,进而注册了服务启动的逻辑。这里我们需要一个 MeaningOfLifeHandler,Fx 会观察到这一点,进而到 Provider 里面挨个找依赖,每个类型对应一个单例对象,通过懒加载的方式获取到 MeaningOfLifeHandler 的所有依赖,以及子依赖。
其实 Invoker 更多意义上看,像是一个触发器。
我们可以有很多 Provider,但什么时候去调用这些函数,生成依赖呢?Invoker 就是做这件事的。
// New creates and initializes an App, immediately executing any functions
// registered via Invoke options. See the documentation of the App struct for
// details on the application's initialization, startup, and shutdown logic.
func New(opts ...Option) *App
最后,有了一个通过 fx.New 生成的 fx 应用,我们就可以通过 Start 方法来启动了:
func main() {
ctx, cancel := context.WithCancel(context.Background())
kill := make(chan os.Signal, 1)
signal.Notify(kill)
go func() {
<-kill
cancel()
}()
app := fx.New(
fx.Provide(newZapLogger),
fx.Provide(newRedisClient),
fx.Provide(newMeaningOfLifeCacheRedis),
fx.Provide(newMeaningOfLifeHandler),
fx.Invoke(runHttpServer),
)
if err := app.Start(ctx);err != nil{
fmt.Println(err)
}
}
当然,有了一个 fx 应用后,我们可以直接 fx.New().Run() 来启动,也可以随后通过 app.Start(ctx) 方法启动,配合 ctx 的取消和超时能力。二者皆可。
fx.In 封装多个入参
当构造函数参数过多的时候,我们可以使用 fx.In 来统一注入,而不用在构造器里一个个加参数:
type ConstructorParam struct {
fx.In
Logger *log.Logger
Handler http.Handler
}
type Object struct {
Logger *log.Logger
Handler http.Handler
}
func NewObject(p ConstructorParam) Object {
return Object {
Logger: p.Logger,
Handler: p.Handler,
}
}
fx.Out 封装多个出参
和 In 类似,有时候我们需要返回多个参数,这时候一个个写显然比较笨重。我们可以用 fx.Out 的能力用结构体来封装:
type Result struct {
fx.Out
Logger *log.Logger
Handler http.Handler
}
func NewResult() Result {
// logger := xxx
// handler := xxx
return Result {
Logger: logger,
Handler: handler,
}
}
基于同类型提供多种实现
By default, Fx applications only allow one constructor for each type.
Fx 应用默认只允许每种类型存在一个构造器,这种限制在一些时候是很痛的。
有些时候我们就是会针对一个 interface 提供多种实现,如果做不到,我们就只能在外面套一个类型,这和前一篇文章中我们提到的 wire 里的处理方式是一样的:
type RedisA *redis.Client
type RedisB *redis.Client
但这样还是很笨重,有没有比较优雅的解决方案呢?
当然有,要不 uber/fx 怎么能被称为一个功能全面的 DI 方案呢?
既然是同类型,多个不同的值,我们可以给不同的实现命名来区分。进而这涉及两个部分:生产端 和 消费端。
在提供依赖的时候,可以声明它的名称,进而即便出现同类型的其他依赖,fx 也知道如何区分。
在获取依赖的时候,也要指明我们需要的依赖的名称具体是什么,而不只是简单的明确类型即可。
这里我们需要用到 fx.In 和 fx.Out 的能力。参照 官方文档 我们来了解一下 fx 的解法:Named Values。
fx 支持开发者声明 name 标签,用来给依赖「起名」,类似这样:name:"rw"。
type GatewayParams struct {
fx.In
WriteToConn *sql.DB `name:"rw"`
ReadFromConn *sql.DB `name:"ro" optional:"true"`
}
func NewCommentGateway(p GatewayParams, log *log.Logger) (*CommentGateway, error) {
if p.ReadFromConn == nil {
log.Print("Warning: Using RW connection for reads")
p.ReadFromConn = p.WriteToConn
}
// ...
}
type ConnectionResult struct {
fx.Out
ReadWrite *sql.DB `name:"rw"`
ReadOnly *sql.DB `name:"ro"`
}
func ConnectToDatabase(...) (ConnectionResult, error) {
// ...
return ConnectionResult{ReadWrite: rw, ReadOnly: ro}, nil
}
这样 fx 就知道,我们去构建 NewCommentGateway 的时候,传入的 *sql.DB 需要是 rw 这个名称的。而此前ConnectToDatabase 已经提供了这个名称,同类型的实例,所以依赖构建成功。
使用起来非常简单,在我们对 In 和 Out 的 wrapper 中声明各个依赖的 name,也可以搭配 optional 标签使用。fx 支持任意多个 name 的实例。
这里需要注意,同名称的生产端和消费端的类型必须一致,不能一个是 sql.DB 另一个是 *sql.DB。命名的能力只有在同类型的情况下才有用处。
Annotate 注解器
Annotate lets you annotate a function's parameters and returns without you having to declare separate struct definitions for them.
注解器能帮我们修改函数的入参和出参,无需定义单独的结构体。fx 的这个能力非常强大,目前暂时没有看到其他 DI 工具能做到这一点。
func Annotate(t interface{}, anns ...Annotation) interface{} {
result := annotated{Target: t}
for _, ann := range anns {
if err := ann.apply(&result); err != nil {
return annotationError{
target: t,
err: err,
}
}
}
return result
}
我们来看看如何用 Annotate 来添加 ParamTag, ResultTag 来实现同一个 interface 多种实现。
// Given,
type Doer interface{ ... }
// And three implementations,
type GoodDoer struct{ ... }
func NewGoodDoer() *GoodDoer
type BadDoer struct{ ... }
func NewBadDoer() *BadDoer
type UglyDoer struct{ ... }
func NewUglyDoer() *UglyDoer
fx.Provide(
fx.Annotate(NewGoodDoer, fx.As(new(Doer)), fx.ResultTags(`name:"good"`)),
fx.Annotate(NewBadDoer, fx.As(new(Doer)), fx.ResultTags(`name:"bad"`)),
fx.Annotate(NewUglyDoer, fx.As(new(Doer)), fx.ResultTags(`name:"ugly"`)),
)
这里我们有 Doer 接口,以及对应的三种实现:GoodDoer, BadDoer, UglyDoer,三种实现的构造器返回值甚至都不需要是Doer,完全可以是自己的 struct 类型。
这里还是不得不感慨 fx 强大的装饰器能力。我们用一个简单的:
fx.Annotate(NewGoodDoer, fx.As(new(Doer)))
就可以对构造器 NewGoodDoer 完成类型转换。
这里还可以写一个 helper 函数简化一下处理:
func AsDoer(f any, name string) any {
return fx.Anntoate(f, fx.As(new(Doer)), fx.ResultTags("name:" + strconv.Quote(name)))
}
fx.Provide(
AsDoer(NewGoodDoer, "good"),
AsDoer(NewBadDoer, "bad"),
AsDoer(NewUglyDoer, "ugly"),
)
与之相对的,提供依赖的时候我们用 ResultTag,消费依赖的时候需要用 ParamTag。
func Do(good, bad, ugly Doer) {
// ...
}
fx.Invoke(
fx.Annotate(Do, fx.ParamTags(`name:"good"`, `name:"bad"`, `name:"ugly"`)),
)
这样就无需通过 fx.In 和 fx.Out 的封装能力来实现了,非常简洁。
当然,如果我们上面的返回值直接就是 interface,那么久不需要 fx.As 这一步转换了。
func NewGateway(ro, rw *db.Conn) *Gateway { ... }
fx.Provide(
fx.Annotate(
NewGateway,
fx.ParamTags(`name:"ro" optional:"true"`, `name:"rw"`),
fx.ResultTags(`name:"foo"`),
),
)
和下面的实现是等价的:
type params struct {
fx.In
RO *db.Conn `name:"ro" optional:"true"`
RW *db.Conn `name:"rw"`
}
type result struct {
fx.Out
GW *Gateway `name:"foo"`
}
fx.Provide(func(p params) result {
return result{GW: NewGateway(p.RO, p.RW)}
})
这里需要注意存在两个限制:
Annotate 不能应用于包含 fx.In 和 fx.Out 的函数,它的存在本身就是为了简化;
不能在一个 Annotate 中多次使用同一个注解,比如下面这个例子会报错:
fx.Provide(
fx.Annotate(
NewGateWay,
fx.ParamTags(`name:"ro" optional:"true"`),
fx.ParamTags(`name:"rw"), // ERROR: ParamTags was already used above
fx.ResultTags(`name:"foo"`)
)
)
小结
这里是 uber/fx 的理论篇,我们了解了 fx 的核心概念和基础用法。和 wire 一样,它们都要求强制编写构造函数,有额外的编码成本。但好处在于功能全面、设计比较优雅,对业务代码无侵入。
下一篇,我们会从实战的角度,基于 cloudwego 社区的 Kitex 框架,看看怎么基于 uber/fx 实现优雅的注入,敬请期待。
参考资料
Simplify dependency injection using Uber FX
fx module
Compose dependency injection with Uber Fx
Is it possible to use multiple implementations for a given interface type?
ag9920
Gopher | CMUer
102
文章
174k
阅读
251
粉丝 目录 收起
开篇
uber/fx
provider 声明依赖关系
module 模块化组织依赖
lifecycle 给应用生命周期加上钩子
invoker 应用的启动器
fx.In 封装多个入参
fx.Out 封装多个出参
基于同类型提供多种实现
Annotate 注解器
小结
参考资料
相关推荐 golang中连接mongo数据库并进行操作 801阅读 · 0点赞go日志框架-zap 286阅读 · 0点赞Golang官方限流器库详解 2.6k阅读 · 21点赞Alibaba/IOC-golang 正式开源 ——打造服务于go开发者的IOC框架 944阅读 · 4点赞自我封装的基于go-gin的web服务框架 2.3k阅读 · 7点赞
golang反射框架Fx - 简书
ng反射框架Fx - 简书登录注册写文章首页下载APP会员IT技术golang反射框架Fx神奇的考拉关注赞赏支持golang反射框架Fx一、概述Fx是一个golang版本的依赖注入框架,它使得golang通过可重用、可组合的模块化来构建golang应用程序变得非常容易,可直接在项目中添加以下内容即可体验Fx效果。
import "github.com/uber-go/fx"
Fx是通过使用依赖注入的方式替换了全局通过手动方式来连接不同函数调用的复杂度,也不同于其他的依赖注入方式,Fx能够像普通golang函数去使用,而不需要通过使用struct标签或内嵌特定类型。这样使得Fx能够在很多go的包中很好的使用。
接下来会提供一些Fx的简单demo,并说明其中的一些定义。
生命周期
二、Fx应用
1、一般步骤
按需定义一些构造函数:主要用于生成依赖注入的具体类型
type FxDemo struct{
// 字段是可导出的,是由于golang的reflection特性决定: 必须能够导出且可寻址才能进行设置
Name string
}
func NewFxDemo(){
return FxDemo{
Name: "hello, world",
}
}
、使用Provide将具体反射的类型添加到container中
可以按需添加任意多个构造函数
fx.Provide(NewFxDemo)
、使用Populate完成变量与具体类型间的映射
var fx FxDemo
fx.Populate(fx)
、新建app对象(application容器包括定义注入变量、类型、不同对象lifecycle等)
app := fx.New(
fx.Provide(NewFxDemo,), // 构造函数可以任意多个
fx.Populate(new(FxDemo)),// 反射变量也可以任意多个,并不需要和上面构造函数对应
)
app.Start(context.Background()) // 开启container
defer app.Stop(context.Background()) // 关闭container
、使用
fmt.Printf("the result is %s \n", fx.Name)
大致的使用步骤就如下。下面会给出一些完整的demo
2、简单demo
将io.reader与具体实现类关联起来
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
"io"
"io/ioutil"
"log"
"strings"
)
func main() {
var reader io.Reader
app := fx.New(
// io.reader的应用
// 提供构造函数
fx.Provide(func() io.Reader {
return strings.NewReader("hello world")
}),
fx.Populate(&reader), // 通过依赖注入完成变量与具体类的映射
)
app.Start(context.Background())
defer app.Stop(context.Background())
// 使用
// reader变量已与fx.Provide注入的实现类关联了
bs, err := ioutil.ReadAll(reader)
if err != nil{
log.Panic("read occur error, ", err)
}
fmt.Printf("the result is '%s' \n", string(bs))
}
输出:
2019/02/02 16:51:57 [Fx] PROVIDE io.Reader <= main.main.func1()
2019/02/02 16:51:57 [Fx] PROVIDE fx.Lifecycle <= fx-master.New.func1()
2019/02/02 16:51:57 [Fx] PROVIDE fx.Shutdowner <= fx-master.(*App).(fx-master.shutdowner)-fm()
2019/02/02 16:51:57 [Fx] PROVIDE fx.DotGraph <= fx-master.(*App).(fx-master.dotGraph)-fm()
2019/02/02 16:51:57 [Fx] INVOKE reflect.makeFuncStub()
2019/02/02 16:51:57 [Fx] RUNNING
the result is 'hello world'
3、使用struct参数
前面的使用方式一旦需要进行注入的类型过多,可以通过struct参数方式来解决
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
)
func main() {
type t3 struct {
Name string
}
type t4 struct {
Age int
}
var (
v1 *t3
v2 *t4
)
app := fx.New(
fx.Provide(func() *t3 { return &t3{"hello everybody!!!"} }),
fx.Provide(func() *t4 { return &t4{2019} }),
fx.Populate(&v1),
fx.Populate(&v2),
)
app.Start(context.Background())
defer app.Stop(context.Background())
fmt.Printf("the reulst is %v , %v\n", v1.Name, v2.Age)
}
输出
2019/02/02 17:00:13 [Fx] PROVIDE *main.t3 <= main.test2.func1()
2019/02/02 17:00:14 [Fx] PROVIDE *main.t4 <= main.test2.func2()
2019/02/02 17:00:14 [Fx] PROVIDE fx.Lifecycle <= fx-master.New.func1()
2019/02/02 17:00:14 [Fx] PROVIDE fx.Shutdowner <= fx-master.(*App).(fx-master.shutdowner)-fm()
2019/02/02 17:00:14 [Fx] PROVIDE fx.DotGraph <= fx-master.(*App).(fx-master.dotGraph)-fm()
2019/02/02 17:00:14 [Fx] INVOKE reflect.makeFuncStub()
2019/02/02 17:00:14 [Fx] INVOKE reflect.makeFuncStub()
2019/02/02 17:00:14 [Fx] RUNNING
the reulst is hello everybody!!! , 2019
如果通过Provide提供构造函数是生成相同类型会有什么问题?换句话也就是相同类型拥有多个值呢?
下面两种方式就是来解决这样的问题。
4、使用struct参数+Name标签
在Fx未使用Name或Group标签时不允许存在多个相同类型的构造函数,一旦存在会触发panic。
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
)
func main() {
type t3 struct {
Name string
}
//name标签的使用
type result struct {
fx.Out
V1 *t3 `name:"n1"`
V2 *t3 `name:"n2"`
}
targets := struct {
fx.In
V1 *t3 `name:"n1"`
V2 *t3 `name:"n2"`
}{}
app := fx.New(
fx.Provide(func() result {
return result{
V1: &t3{"hello-HELLO"},
V2: &t3{"world-WORLD"},
}
}),
fx.Populate(&targets),
)
app.Start(context.Background())
defer app.Stop(context.Background())
fmt.Printf("the result is %v, %v \n", targets.V1.Name, targets.V2.Name)
}
输出
2019/02/02 17:12:02 [Fx] PROVIDE *main.t3:n1 <= main.test3.func1()
2019/02/02 17:12:02 [Fx] PROVIDE *main.t3:n2 <= main.test3.func1()
2019/02/02 17:12:02 [Fx] PROVIDE fx.Lifecycle <= fx-master.New.func1()
2019/02/02 17:12:02 [Fx] PROVIDE fx.Shutdowner <= fx-master.(*App).(fx-master.shutdowner)-fm()
2019/02/02 17:12:02 [Fx] PROVIDE fx.DotGraph <= fx-master.(*App).(fx-master.dotGraph)-fm()
2019/02/02 17:12:02 [Fx] INVOKE reflect.makeFuncStub()
2019/02/02 17:12:02 [Fx] RUNNING
the result is hello-HELLO, world-WORLD
上面通过Name标签即可完成在Fx容器注入相同类型
5、使用struct参数+Group标签
使用group标签同样也能完成上面的功能
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
)
func main() {
type t3 struct {
Name string
}
// 使用group标签
type result struct {
fx.Out
V1 *t3 `group:"g"`
V2 *t3 `group:"g"`
}
targets := struct {
fx.In
Group []*t3 `group:"g"`
}{}
app := fx.New(
fx.Provide(func() result {
return result{
V1: &t3{"hello-000"},
V2: &t3{"world-www"},
}
}),
fx.Populate(&targets),
)
app.Start(context.Background())
defer app.Stop(context.Background())
for _,t := range targets.Group{
fmt.Printf("the result is %v\n", t.Name)
}
}
输出
2019/02/02 17:15:49 [Fx] PROVIDE *main.t3 <= main.test4.func1()
2019/02/02 17:15:49 [Fx] PROVIDE *main.t3 <= main.test4.func1()
2019/02/02 17:15:49 [Fx] PROVIDE fx.Lifecycle <= fx-master.New.func1()
2019/02/02 17:15:49 [Fx] PROVIDE fx.Shutdowner <= fx-master.(*App).(fx-master.shutdowner)-fm()
2019/02/02 17:15:49 [Fx] PROVIDE fx.DotGraph <= fx-master.(*App).(fx-master.dotGraph)-fm()
2019/02/02 17:15:49 [Fx] INVOKE reflect.makeFuncStub()
2019/02/02 17:15:49 [Fx] RUNNING
the result is hello-000
the result is world-www
基本上Fx简单应用在上面的例子也做了简单讲解
三、Fx定义
1、Annotated(位于annotated.go文件) 主要用于采用annotated的方式,提供Provide注入类型
type t3 struct {
Name string
}
targets := struct {
fx.In
V1 *t3 `name:"n1"`
}{}
app := fx.New(
fx.Provide(fx.Annotated{
Name:"n1",
Target: func() *t3{
return &t3{"hello world"}
},
}),
fx.Populate(&targets),
)
app.Start(context.Background())
defer app.Stop(context.Background())
fmt.Printf("the result is = '%v'\n", targets.V1.Name)
源码中Name和Group两个字段与前面提到的Name标签和Group标签是一样的,只能选其一使用
2、App(位于app.go文件) 提供注入对象具体的容器、LiftCycle、容器的启动及停止、类型变量及实现类注入和两者映射等操作
type App struct {
err error
container *dig.Container // 容器
lifecycle *lifecycleWrapper // 生命周期
provides []interface{} // 注入的类型实现类
invokes []interface{}
logger *fxlog.Logger
startTimeout time.Duration
stopTimeout time.Duration
errorHooks []ErrorHandler
donesMu sync.RWMutex
dones []chan os.Signal
}
// 新建一个App对象
func New(opts ...Option) *App {
logger := fxlog.New() // 记录Fx日志
lc := &lifecycleWrapper{lifecycle.New(logger)} // 生命周期
app := &App{
container: dig.New(dig.DeferAcyclicVerification()),
lifecycle: lc,
logger: logger,
startTimeout: DefaultTimeout,
stopTimeout: DefaultTimeout,
}
for _, opt := range opts { // 提供的Provide和Populate的操作
opt.apply(app)
}
// 进行app相关一些操作
for _, p := range app.provides {
app.provide(p)
}
app.provide(func() Lifecycle { return app.lifecycle })
app.provide(app.shutdowner)
app.provide(app.dotGraph)
if app.err != nil { // 记录app初始化过程是否正常
app.logger.Printf("Error after options were applied: %v", app.err)
return app
}
// 执行invoke
if err := app.executeInvokes(); err != nil {
app.err = err
if dig.CanVisualizeError(err) {
var b bytes.Buffer
dig.Visualize(app.container, &b, dig.VisualizeError(err))
err = errorWithGraph{
graph: b.String(),
err: err,
}
}
errorHandlerList(app.errorHooks).HandleError(err)
}
return app
}
至于Provide和Populate的源码相对比较简单易懂在这里不在描述
具体源码
3、Extract(位于extract.go文件)
主要用于在application启动初始化过程通过依赖注入的方式将容器中的变量值来填充给定的struct,其中target必须是指向struct的指针,并且只能填充可导出的字段(golang只能通过反射修改可导出并且可寻址的字段),Extract将被Populate代替。具体源码
4、其他
诸如Populate是用来替换Extract的,而LiftCycle和inout.go涉及内容比较多后续会单独提供专属文件说明。
四、其他
在Fx中提供的构造函数都是惰性调用,可以通过invocations在application启动来完成一些必要的初始化工作:fx.Invoke(function); 通过也可以按需自定义实现LiftCycle的Hook对应的OnStart和OnStop用来完成手动启动容器和关闭,来满足一些自己实际的业务需求。
package main
import (
"context"
"log"
"net/http"
"os"
"time"
"go.uber.org/fx"
)
// Logger构造函数
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
// http.Handler构造函数
func NewHandler(logger *log.Logger) (http.Handler, error) {
logger.Print("Executing NewHandler.")
return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
logger.Print("Got a request.")
}), nil
}
// http.ServeMux构造函数
func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
logger.Print("Executing NewMux.")
mux := http.NewServeMux()
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
lc.Append(fx.Hook{ // 自定义生命周期过程对应的启动和关闭的行为
OnStart: func(context.Context) error {
logger.Print("Starting HTTP server.")
go server.ListenAndServe()
return nil
},
OnStop: func(ctx context.Context) error {
logger.Print("Stopping HTTP server.")
return server.Shutdown(ctx)
},
})
return mux
}
// 注册http.Handler
func Register(mux *http.ServeMux, h http.Handler) {
mux.Handle("/", h)
}
func main() {
app := fx.New(
fx.Provide(
NewLogger,
NewHandler,
NewMux,
),
fx.Invoke(Register), // 通过invoke来完成Logger、Handler、ServeMux的创建
)
startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil { // 手动调用Start
log.Fatal(err)
}
http.Get("http://localhost:8080/") // 具体操作
stopCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Stop(stopCtx); err != nil { // 手动调用Stop
log.Fatal(err)
}
}
五、Fx源码解析
Fx框架源码解析
主要包括app.go、lifecycle.go、annotated.go、populate.go、inout.go、shutdown.go、extract.go(可以忽略,了解populate.go)以及辅助的internal中的fxlog、fxreflect、lifecycle
最后编辑于 :2019.02.12 13:47:33©著作权归作者所有,转载或内容合作请联系作者人面猴序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...沈念sama阅读 145,261评论 1赞 308死咒序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...沈念sama阅读 62,177评论 1赞 259救了他两次的神仙让他今天三更去死文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...开封第一讲书人阅读 96,329评论 0赞 214道士缉凶录:失踪的卖姜人 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...开封第一讲书人阅读 41,490评论 0赞 184港岛之恋(遗憾婚礼)正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...茶点故事阅读 49,353评论 1赞 262恶毒庶女顶嫁案:这布局不是一般人想出来的文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...开封第一讲书人阅读 39,028评论 1赞 179城市分裂传说那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...沈念sama阅读 30,611评论 2赞 276双鸳鸯连环套:你想象不到人心有多黑文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...开封第一讲书人阅读 29,383评论 0赞 171万荣杀人案实录序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...沈念sama阅读 32,749评论 0赞 215护林员之死正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...茶点故事阅读 29,460评论 2赞 219白月光启示录正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...茶点故事阅读 30,814评论 1赞 232活死人序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...沈念sama阅读 27,255评论 2赞 215日本核电站爆炸内幕正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...茶点故事阅读 31,752评论 3赞 214男人毒药:我在死后第九天来索命文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...开封第一讲书人阅读 25,685评论 0赞 9一桩弑父案,背后竟有这般阴谋文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...开封第一讲书人阅读 26,114评论 0赞 170情欲美人皮我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...沈念sama阅读 33,747评论 2赞 234代替公主和亲正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...茶点故事阅读 33,901评论 2赞 238评论2赞1414赞15赞赞赏更
GitHub - uber-go/fx: A dependency injection based application framework for Go.
GitHub - uber-go/fx: A dependency injection based application framework for Go.
Skip to content
Toggle navigation
Sign in
Product
Actions
Automate any workflow
Packages
Host and manage packages
Security
Find and fix vulnerabilities
Codespaces
Instant dev environments
Copilot
Write better code with AI
Code review
Manage code changes
Issues
Plan and track work
Discussions
Collaborate outside of code
Explore
All features
Documentation
GitHub Skills
Blog
Solutions
For
Enterprise
Teams
Startups
Education
By Solution
CI/CD & Automation
DevOps
DevSecOps
Resources
Learning Pathways
White papers, Ebooks, Webinars
Customer Stories
Partners
Open Source
GitHub Sponsors
Fund open source developers
The ReadME Project
GitHub community articles
Repositories
Topics
Trending
Collections
Pricing
Search or jump to...
Search code, repositories, users, issues, pull requests...
Search
Clear
Search syntax tips
Provide feedback
We read every piece of feedback, and take your input very seriously.
Include my email address so I can be contacted
Cancel
Submit feedback
Saved searches
Use saved searches to filter your results more quickly
Name
Query
To see all available qualifiers, see our documentation.
Cancel
Create saved search
Sign in
Sign up
You signed in with another tab or window. Reload to refresh your session.
You signed out in another tab or window. Reload to refresh your session.
You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
uber-go
/
fx
Public
Notifications
Fork
266
Star
5k
A dependency injection based application framework for Go.
uber-go.github.io/fx/
License
MIT license
5k
stars
266
forks
Branches
Tags
Activity
Star
Notifications
Code
Issues
47
Pull requests
4
Discussions
Actions
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Discussions
Actions
Security
Insights
uber-go/fx
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
masterBranchesTagsGo to fileCodeFolders and filesNameNameLast commit messageLast commit dateLatest commit History963 Commits.github.github docsdocs fxeventfxevent fxtestfxtest internalinternal toolstools .codecov.yml.codecov.yml .gitignore.gitignore .golangci.yml.golangci.yml CHANGELOG.mdCHANGELOG.md CONTRIBUTING.mdCONTRIBUTING.md LICENSELICENSE MakefileMakefile README.mdREADME.md annotated.goannotated.go annotated_test.goannotated_test.go app.goapp.go app_internal_test.goapp_internal_test.go app_test.goapp_test.go app_unixes.goapp_unixes.go app_wasm.goapp_wasm.go app_windows.goapp_windows.go app_windows_test.goapp_windows_test.go decorate.godecorate.go decorate_test.godecorate_test.go doc.godoc.go error_example_test.goerror_example_test.go example_test.goexample_test.go extract.goextract.go extract_test.goextract_test.go go.modgo.mod go.sumgo.sum inout.goinout.go inout_test.goinout_test.go invoke.goinvoke.go lifecycle.golifecycle.go log.golog.go log_test.golog_test.go module.gomodule.go module_test.gomodule_test.go populate.gopopulate.go populate_example_test.gopopulate_example_test.go populate_test.gopopulate_test.go printer_writer.goprinter_writer.go provide.goprovide.go replace.goreplace.go replace_test.goreplace_test.go shutdown.goshutdown.go shutdown_test.goshutdown_test.go signal.gosignal.go signal_test.gosignal_test.go supply.gosupply.go supply_test.gosupply_test.go version.goversion.go View all filesRepository files navigationREADMEMIT license Fx
Fx is a dependency injection system for Go.
Benefits
Eliminate globals: Fx helps you remove global state from your application.
No more init() or global variables. Use Fx-managed singletons.
Code reuse: Fx lets teams within your organization build loosely-coupled
and well-integrated shareable components.
Battle tested: Fx is the backbone of nearly all Go services at Uber.
See our docs to get started and/or
learn more about Fx.
Installation
Use Go modules to install Fx in your application.
go get go.uber.org/fx@v1
Getting started
To get started with Fx, start here.
Stability
This library is v1 and follows SemVer strictly.
No breaking changes will be made to exported APIs before v2.0.0.
This project follows the Go Release Policy. Each major
version of Go is supported until there are two newer major releases.
Stargazers over time
About
A dependency injection based application framework for Go.
uber-go.github.io/fx/
Topics
go
golang
framework
service
dependency-injection
app-framework
Resources
Readme
License
MIT license
Activity
Custom properties
Stars
5k
stars
Watchers
65
watching
Forks
266
forks
Report repository
Releases
39
v1.20.1
Latest
Oct 17, 2023
+ 38 releases
Packages
0
No packages published
Contributors
65
+ 51 contributors
Languages
Go
99.7%
Makefile
0.3%
Footer
© 2024 GitHub, Inc.
Footer navigation
Terms
Privacy
Security
Status
Docs
Contact
Manage cookies
Do not share my personal information
You can’t perform that action at this time.
Go 依赖注入系统 - Fx 官方文档
Go 依赖注入系统 - Fx 官方文档
1. 简介2. 入门教程2.1. 创建最小的应用程序2.2. 添加 HTTP 服务2.3. 注册处理器2.4. 添加 Logger2.5. 解耦注册2.6. 注册另一个处理器2.7. 注册多个处理器3. 概念3.1. 应用程序生命周期3.1.1. 生命周期钩子3.2. 模块3.2.1. 编写模块3.2.1.1. 命名3.2.1.1.1. 包3.2.1.1.2. 参数和结果对象3.2.1.2. 导出边界函数3.2.1.3. 使用参数对象3.2.1.4. 使用结果对象3.2.1.5. 不要提供你不拥有的东西3.2.1.6. 保持独立模块精简3.2.1.7. 谨慎地使用 Invoke4. 特性4.1. 参数对象4.1.1. 使用参数对象4.1.2. 添加新参数4.2. 结果对象4.2.1. 使用结果对象4.2.2. 添加新结果4.3. 注解(Annotation)4.3.1. 对函数进行注解4.3.2. 将结构体强制转换为接口4.4. 值组(Value Group)4.4.1. 使用值组4.4.2. 依赖严格性4.4.2.1. 严格的值组4.4.2.2. 软值组4.4.3. 向值组提供值4.4.3.1. 使用结果对象4.4.3.2. 使用带注解的函数4.4.4. 从值组获取值4.4.4.1. 使用参数对象4.4.4.2. 使用带注解的函数Go 依赖注入系统 - Fx 官方文档官方文档:https://uber-go.github.io/fx/get-started/1. 简介Fx 是用于 Go 的依赖注入系统。使用 Fx 可以:在设置应用程序时,减少样板文件消除应用程序中的全局状态添加新组件,并且可以立即在整个应用程序中进行访问构建通用的可共享模块2. 入门教程本章将介绍 Fx 的基本用法。首先,做准备工作。创建新项目mkdir fxdemocd fxdemogo mod init example.com/fxdemo安装最新版本的 Fxgo get go.uber.org/fx@latest2.1. 创建最小的应用程序下面构建 Fx 版的 hello-world,该应用程序除打印一堆日志外,不做任何事情。编写最小化的 main.go:package main
import "go.uber.org/fx"
func main() { fx.New().Run()}运行应用程序:go run .将看到类似下面的输出:[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()[Fx] RUNNING该输出展示提供给 Fx 应用程序的默认对象,但是它不做任何有意义的事情。使用 Ctrl-C 停止应用程序。[Fx] RUNNING^C[Fx] INTERRUPT我们刚刚做了什么?通过不带参数地调用 fx.New,构建空 Fx 应用程序。但是应用程序通常会向 fx.New 传递参数,以设置其组件。然后,使用 App.Run 方法运行该应用程序。该方法将阻塞到接收到停止信号,在退出前,该方法将运行必要的清理操作。Fx 主要用于常驻的服务端应用程序;这些应用程序在关闭时通常从部署系统接收信号。2.2. 添加 HTTP 服务下面向前一节的 Fx 应用程序中添加 HTTP 服务。编写构建 HTTP 服务的函数。// NewHTTPServer builds an HTTP server that will begin serving requests// when the Fx application starts.func NewHTTPServer(lc fx.Lifecycle) *http.Server { srv := &http.Server{Addr: ":8080"} return srv}这还不够,我们需要告诉 Fx 如何启动 HTTP 服务。这就是 fx.Lifecycle 参数的作用。使用 fx.Lifecycle 对象向应用程序添加 lifecyle 钩子。告诉 Fx 如何启动和停止 HTTP 服务。func NewHTTPServer(lc fx.Lifecycle) *http.Server { srv := &http.Server{Addr: ":8080"} lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { ln, err := net.Listen("tcp", srv.Addr) if err != nil { return err } fmt.Println("Starting HTTP server at", srv.Addr) go srv.Serve(ln) return nil }, OnStop: func(ctx context.Context) error { return srv.Shutdown(ctx) }, }) return srv}使用 fx.Provide 将其提供给上面的 Fx 应用程序。xxxxxxxxxxfunc main() { fx.New( fx.Provide(NewHTTPServer), ).Run()}运行应用程序。xxxxxxxxxx[Fx] PROVIDE *http.Server <= main.NewHTTPServer()[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()[Fx] RUNNING输出中的第一行表明已提供 Server,但是输出不包含“Starting HTTP server”消息。因此服务未运行。为解决该问题,添加 fx.Invoke,请求被构造的 Server。xxxxxxxxxx fx.New( fx.Provide(NewHTTPServer), fx.Invoke(func(*http.Server) {}), ).Run()再次运行应用程序。这次可以在输出中看到“Starting HTTP server”。xxxxxxxxxx[Fx] PROVIDE *http.Server <= main.NewHTTPServer()[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()[Fx] INVOKE main.main.func1()[Fx] RUN provide: go.uber.org/fx.New.func1()[Fx] RUN provide: main.NewHTTPServer()[Fx] HOOK OnStart main.NewHTTPServer.func1() executing (caller: main.NewHTTPServer)Starting HTTP server at :8080[Fx] HOOK OnStart main.NewHTTPServer.func1() called by main.NewHTTPServer ran successfully in 335.758µs[Fx] RUNNING向服务发送请求。xxxxxxxxxx$ curl http://localhost:8080404 page not found请求是 404,因为服务还不知道如何处理请求。下一节将修复该问题。停止应用程序。xxxxxxxxxx^C[Fx] INTERRUPT[Fx] HOOK OnStop main.NewHTTPServer.func2() executing (caller: main.NewHTTPServer)[Fx] HOOK OnStop main.NewHTTPServer.func2() called by main.NewHTTPServer ran successfully in 129.875µs我们刚刚做了什么?使用 fx.Provide 向应用程序添加 HTTP Server。Server 连接到 Fx 应用程序的生命周期 -- 当调用 App.Run 时,开始服务请求,当应用程序收到停止信号时,停止运行。使用 fx.Invoke,保证始终实例化 HTTP Server,即便在应用程序中没有其它组件直接引用它。后面将介绍 Fx 生命周期是什么,以及如何使用。2.3. 注册处理器前一节构造的 Server 可以接收请求,但是该服务不知道如何处理请求。下面修复该问题。定义基础的 HTTP 处理器,该处理器将传入的请求体拷贝到响应。在文件底部添加如下代码。xxxxxxxxxx// EchoHandler is an http.Handler that copies its request body// back to the response.type EchoHandler struct{}
// NewEchoHandler builds a new EchoHandler.func NewEchoHandler() *EchoHandler { return &EchoHandler{}}
// ServeHTTP handles an HTTP request to the /echo endpoint.func (*EchoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if _, err := io.Copy(w, r.Body); err != nil { fmt.Fprintln(os.Stderr, "Failed to handle request:", err) }}将其提供给应用程序:xxxxxxxxxx fx.Provide( NewHTTPServer, NewEchoHandler, ), fx.Invoke(func(*http.Server) {}),下面,编写构造 *http.ServeMux 的函数。*http.ServeMux 将服务接收到的请求路由到不同的处理器。在本例中,它将发送到 /echo 的请求路由到 *EchoHandler,因此其构造器接受 *EchoHandler 作为参数。xxxxxxxxxx// NewServeMux builds a ServeMux that will route requests// to the given EchoHandler.func NewServeMux(echo *EchoHandler) *http.ServeMux { mux := http.NewServeMux() mux.Handle("/echo", echo) return mux}同样地,将其提供给应用程序。xxxxxxxxxx fx.Provide( NewHTTPServer, NewServeMux, NewEchoHandler, ),注意,提供给 fx.Provide 的构造器的顺序无关紧要。最后,修改 NewHTTPServer 函数,将 Server 连接到该 *ServeMux。xxxxxxxxxxfunc NewHTTPServer(lc fx.Lifecycle, mux *http.ServeMux) *http.Server { srv := &http.Server{Addr: ":8080", Handler: mux} lc.Append(fx.Hook{运行 Server。xxxxxxxxxx[Fx] PROVIDE *http.Server <= main.NewHTTPServer()[Fx] PROVIDE *http.ServeMux <= main.NewServeMux()[Fx] PROVIDE *main.EchoHandler <= main.NewEchoHandler()[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()[Fx] INVOKE main.main.func1()[Fx] RUN provide: go.uber.org/fx.New.func1()[Fx] RUN provide: main.NewEchoHandler()[Fx] RUN provide: main.NewServeMux()[Fx] RUN provide: main.NewHTTPServer()[Fx] HOOK OnStart main.NewHTTPServer.func1() executing (caller: main.NewHTTPServer)Starting HTTP server at :8080[Fx] HOOK OnStart main.NewHTTPServer.func1() called by main.NewHTTPServer ran successfully in 215.434µs[Fx] RUNNING向 Server 发送请求。xxxxxxxxxx$ curl -X POST -d 'hello' http://localhost:8080/echohello我们刚刚做了什么?使用 fx.Provide 添加更多组件。这些组件通过在构造器中添加参数的方式,声明彼此之间的依赖关系。Fx 将通过参数和函数的返回值,解析组件的依赖关系。2.4. 添加 Logger当前的应用程序将“Starting HTTP server”消息打印到标准输出,将错误打印到标准错误输出。两者都是全局状态。我们应该打印到 Logger 对象。本教程使用 Zap,但也可以使用任何日志记录系统。将 Zap Logger 提供给应用程序。本教程使用 zap.NewExample,但真实的应用程序应该使用 zap.NewProduction,或者构建更加定制化的 Logger。xxxxxxxxxx fx.Provide( NewHTTPServer, NewServeMux, NewEchoHandler, zap.NewExample, ),在 EchoHandler 上添加持有 Logger 的字段,并且在 NewEchoHandler 中添加设置该字段的参数。xxxxxxxxxxtype EchoHandler struct { log *zap.Logger}
func NewEchoHandler(log *zap.Logger) *EchoHandler { return &EchoHandler{log: log}}在 EchoHandler.ServeHTTP 方法中,使用 Logger 代替向标准错误输出打印。xxxxxxxxxxfunc (h *EchoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if _, err := io.Copy(w, r.Body); err != nil { h.log.Warn("Failed to handle request", zap.Error(err)) }}类似地,更新 NewHTTPServer,接收 Logger,并且将“Starting HTTP server”消息记录到该 Logger。xxxxxxxxxxfunc NewHTTPServer(lc fx.Lifecycle, mux *http.ServeMux, log *zap.Logger) *http.Server { srv := &http.Server{Addr: ":8080", Handler: mux} lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { ln, err := net.Listen("tcp", srv.Addr) if err != nil { return err } log.Info("Starting HTTP server", zap.String("addr", srv.Addr)) go srv.Serve(ln)(可选)也可以使用同一 Zap Logger 记录 Fx 自身的日志。xxxxxxxxxxfunc main() { fx.New( fx.WithLogger(func(log *zap.Logger) fxevent.Logger { return &fxevent.ZapLogger{Logger: log} }),这将使用打印到 Logger 的消息替换 [Fx] 消息。运行应用程序。xxxxxxxxxx{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"}{"level":"info","msg":"provided","constructor":"main.NewServeMux()","type":"*http.ServeMux"}{"level":"info","msg":"provided","constructor":"main.NewEchoHandler()","type":"*main.EchoHandler"}{"level":"info","msg":"provided","constructor":"go.uber.org/zap.NewExample()","type":"*zap.Logger"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.New.func1()","type":"fx.Lifecycle"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).shutdowner-fm()","type":"fx.Shutdowner"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).dotGraph-fm()","type":"fx.DotGraph"}{"level":"info","msg":"initialized custom fxevent.Logger","function":"main.main.func1()"}{"level":"info","msg":"invoking","function":"main.main.func2()"}{"level":"info","msg":"OnStart hook executing","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer"}{"level":"info","msg":"Starting HTTP server","addr":":8080"}{"level":"info","msg":"OnStart hook executed","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer","runtime":"6.292µs"}{"level":"info","msg":"started"}向 Server POST 请求。xxxxxxxxxx$ curl -X POST -d 'hello' http://localhost:8080/echohello我们刚刚做了什么?使用 fx.Provide 将另一个组件添加到应用程序,并且将其注入进需要打印日志的其它组件。为实现这一点,只需要在构造器中添加新参数。在可选步骤中,告诉 Fx 我们希望为 Fx 自身的操作提供自定义 Logger。使用现有的 fxevent.ZapLogger 从注入的 Logger 构建该自定义 Logger,以使所有日志遵循相同的格式。2.5. 解耦注册前面的 NewServeMux 明确声明对 EchoHandler 的依赖。这是不必要的紧耦合。ServeMux 是否真得需要知道确切的处理器实现?如果为 ServeMux 编写测试,那么不应该构造 EchoHandler。下面尝试修复该问题。在 main.go 中定义 Route 类型。它是 http.Handler 的扩展,该处理器知道其注册路径。xxxxxxxxxx// Route is an http.Handler that knows the mux pattern// under which it will be registered.type Route interface { http.Handler
// Pattern reports the path at which this is registered. Pattern() string}修改 EchoHandler 实现该接口。xxxxxxxxxxfunc (*EchoHandler) Pattern() string { return "/echo"}在 main() 中,对 NewEchoHandler 条目做注解,以声明将该处理器当作 Route 提供给应用程序。xxxxxxxxxx fx.Provide( NewHTTPServer, NewServeMux, fx.Annotate( NewEchoHandler, fx.As(new(Route)), ), zap.NewExample, ),修改 NewServeMux 接受 Route,并且使用其提供的模式。xxxxxxxxxx// NewServeMux builds a ServeMux that will route requests// to the given Route.func NewServeMux(route Route) *http.ServeMux { mux := http.NewServeMux() mux.Handle(route.Pattern(), route) return mux}运行服务。xxxxxxxxxx{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"}{"level":"info","msg":"provided","constructor":"main.NewServeMux()","type":"*http.ServeMux"}{"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewEchoHandler(), fx.As([[main.Route]])","type":"main.Route"}{"level":"info","msg":"provided","constructor":"go.uber.org/zap.NewExample()","type":"*zap.Logger"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.New.func1()","type":"fx.Lifecycle"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).shutdowner-fm()","type":"fx.Shutdowner"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).dotGraph-fm()","type":"fx.DotGraph"}{"level":"info","msg":"initialized custom fxevent.Logger","function":"main.main.func1()"}{"level":"info","msg":"invoking","function":"main.main.func2()"}{"level":"info","msg":"OnStart hook executing","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer"}{"level":"info","msg":"Starting HTTP server","addr":":8080"}{"level":"info","msg":"OnStart hook executed","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer","runtime":"10.125µs"}{"level":"info","msg":"started"}向其发送请求。xxxxxxxxxx$ curl -X POST -d 'hello' http://localhost:8080/echohello我们刚刚做了什么?引入接口从使用者解耦实现。然后使用 fx.Annotate 和 fx.As 对前面提供的构造器做注解,以将其结果强制转换为接口。通过这种方式,NewEchoHandler 可以继续返回 *EchoHandler。2.6. 注册另一个处理器下面添加另一个处理器。在同一文件中创建新处理器。xxxxxxxxxx// HelloHandler is an HTTP handler that// prints a greeting to the user.type HelloHandler struct { log *zap.Logger}
// NewHelloHandler builds a new HelloHandler.func NewHelloHandler(log *zap.Logger) *HelloHandler { return &HelloHandler{log: log}}为该处理器实现 Route 接口。xxxxxxxxxxfunc (*HelloHandler) Pattern() string { return "/hello"}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { h.log.Error("Failed to read request", zap.Error(err)) http.Error(w, "Internal server error", http.StatusInternalServerError) return }
if _, err := fmt.Fprintf(w, "Hello, %s\n", body); err != nil { h.log.Error("Failed to write response", zap.Error(err)) http.Error(w, "Internal server error", http.StatusInternalServerError) return }}该处理器读取其请求体,并且向调用者返回欢迎消息。将其作为 Route 提供给应用程序,放在 NewEchoHandler 旁边。xxxxxxxxxx fx.Annotate( NewEchoHandler, fx.As(new(Route)), ), fx.Annotate( NewHelloHandler, fx.As(new(Route)), ),运行应用程序 - 服务将启动失败。xxxxxxxxxx[Fx] PROVIDE *http.Server <= main.NewHTTPServer()[Fx] PROVIDE *http.ServeMux <= main.NewServeMux()[Fx] PROVIDE main.Route <= fx.Annotate(main.NewEchoHandler(), fx.As([[main.Route]])[Fx] Error after options were applied: fx.Provide(fx.Annotate(main.NewHelloHandler(), fx.As([[main.Route]])) from:[...][Fx] ERROR Failed to start: the following errors occurred: - fx.Provide(fx.Annotate(main.NewHelloHandler(), fx.As([[main.Route]])) from: [...] Failed: cannot provide function "main".NewHelloHandler ([..]/main.go:53): cannot provide main.Route from [0].Field0: already provided by "main".NewEchoHandler ([..]/main.go:80)输出很多,但在错误信息内部,可以看到:xxxxxxxxxxcannot provide main.Route from [0].Field0: already provided by "main".NewEchoHandler ([..]/main.go:80)失败原因是 Fx 不允许容器中存在相同类型的两个实例,并且未对它们进行注解。NewServeMux 不知道使用哪个 Route。下面进行修复。在 main() 中,使用名称对 NewEchoHandler 和 NewHelloHandler 进行注解。xxxxxxxxxx fx.Annotate( NewEchoHandler, fx.As(new(Route)), fx.ResultTags(`name:"echo"`), ), fx.Annotate( NewHelloHandler, fx.As(new(Route)), fx.ResultTags(`name:"hello"`), ),向 NewServeMux 添加另一个 Route 参数。xxxxxxxxxx// NewServeMux builds a ServeMux that will route requests// to the given routes.func NewServeMux(route1, route2 Route) *http.ServeMux { mux := http.NewServeMux() mux.Handle(route1.Pattern(), route1) mux.Handle(route2.Pattern(), route2) return mux}在 main() 中,对 NewServeMux 进行注解,以使用这两个名称值。xxxxxxxxxx fx.Provide( NewHTTPServer, fx.Annotate( NewServeMux, fx.ParamTags(`name:"echo"`, `name:"hello"`), ),运行程序。xxxxxxxxxx{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"}{"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewServeMux(), fx.ParamTags([\"name:\\\"echo\\\"\" \"name:\\\"hello\\\"\"])","type":"*http.ServeMux"}{"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewEchoHandler(), fx.ResultTags([\"name:\\\"echo\\\"\"]), fx.As([[main.Route]])","type":"main.Route[name = \"echo\"]"}{"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewHelloHandler(), fx.ResultTags([\"name:\\\"hello\\\"\"]), fx.As([[main.Route]])","type":"main.Route[name = \"hello\"]"}{"level":"info","msg":"provided","constructor":"go.uber.org/zap.NewExample()","type":"*zap.Logger"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.New.func1()","type":"fx.Lifecycle"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).shutdowner-fm()","type":"fx.Shutdowner"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).dotGraph-fm()","type":"fx.DotGraph"}{"level":"info","msg":"initialized custom fxevent.Logger","function":"main.main.func1()"}{"level":"info","msg":"invoking","function":"main.main.func2()"}{"level":"info","msg":"OnStart hook executing","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer"}{"level":"info","msg":"Starting HTTP server","addr":":8080"}{"level":"info","msg":"OnStart hook executed","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer","runtime":"56.334µs"}{"level":"info","msg":"started"}发送请求。xxxxxxxxxx$ curl -X POST -d 'hello' http://localhost:8080/echohello
$ curl -X POST -d 'gopher' http://localhost:8080/helloHello, gopher我们刚刚做了什么?添加构造器,用于生成与现有类型相同类型的值。使用 fx.ResultTags 对构造器进行注解,生成命名值,使用 fx.ParamTags 对使用者进行注解,使用这些命名值。2.7. 注册多个处理器前面的示例有两个处理器,但是在构造 NewServeMux 时,通过名称显式地引用它们。如果添加更多处理器,这种方式将变得不方便。如果 NewServeMux 无需知道有多少个处理器或者名称,而只接受待注册的处理器列表,那么更可取。修改 NewServeMux,使其操作 Route 对象的列表。xxxxxxxxxxfunc NewServeMux(routes []Route) *http.ServeMux { mux := http.NewServeMux() for _, route := range routes { mux.Handle(route.Pattern(), route) } return mux}在 main 中,对 NewServeMux 条目进行注解,以说明它接收包含“routes”组内容的切片。xxxxxxxxxx fx.Provide( NewHTTPServer, fx.Annotate( NewServeMux, fx.ParamTags(`group:"routes"`), ),定义新函数 AsRoute,以构造提供给该组的函数。xxxxxxxxxx// AsRoute annotates the given constructor to state that// it provides a route to the "routes" group.func AsRoute(f any) any { return fx.Annotate( f, fx.As(new(Route)), fx.ResultTags(`group:"routes"`), )}在 main() 中,使用 AsRoute 包装 NewEchoHandler 和 NewHelloHandler 构造器,以便它们将路由提供给该组。xxxxxxxxxx fx.Provide( AsRoute(NewEchoHandler), AsRoute(NewHelloHandler), zap.NewExample, ),最后,运行该应用程序。xxxxxxxxxx{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"}{"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewServeMux(), fx.ParamTags([\"group:\\\"routes\\\"\"])","type":"*http.ServeMux"}{"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewEchoHandler(), fx.ResultTags([\"group:\\\"routes\\\"\"]), fx.As([[main.Route]])","type":"main.Route[group = \"routes\"]"}{"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewHelloHandler(), fx.ResultTags([\"group:\\\"routes\\\"\"]), fx.As([[main.Route]])","type":"main.Route[group = \"routes\"]"}{"level":"info","msg":"provided","constructor":"go.uber.org/zap.NewExample()","type":"*zap.Logger"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.New.func1()","type":"fx.Lifecycle"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).shutdowner-fm()","type":"fx.Shutdowner"}{"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).dotGraph-fm()","type":"fx.DotGraph"}{"level":"info","msg":"initialized custom fxevent.Logger","function":"main.main.func1()"}{"level":"info","msg":"invoking","function":"main.main.func2()"}{"level":"info","msg":"OnStart hook executing","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer"}{"level":"info","msg":"Starting HTTP server","addr":":8080"}{"level":"info","msg":"OnStart hook executed","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer","runtime":"5µs"}{"level":"info","msg":"started"}发送请求。xxxxxxxxxx$ curl -X POST -d 'hello' http://localhost:8080/echohello
$ curl -X POST -d 'gopher' http://localhost:8080/helloHello, gopher我们刚刚做了什么?对 NewServeMux 进行注解,使其将值组当作切片使用,并且对现有处理器构造器进行注解,将其提供给该值组。应用程序中的任何构造函器只要结果符合 Route 接口,都可以向该值组提供值。它们将被收集在一起,并且被传递给 ServeMux 构造器。3. 概念3.1. 应用程序生命周期Fx 应用程序的生命周期有两个阶段:初始化和执行。这两个阶段依次由多个步骤组成。在初始化期间,Fx 将:注册传递给 fx.Provide 的所有构造器注册传递给 fx.Decorate 的所有装饰器运行传递给 fx.Invoke 的所有函数,按需调用构造器和装饰器在执行期间,Fx 将:运行由 Provider、Decorator、Invoked 函数追加到应用程序的所有启动钩子等待停止运行的信号运行追加到应用程序的所有关闭钩子3.1.1. 生命周期钩子当应用程序启动或关闭时,生命周期钩子提供调度 Fx 执行的工作的能力。Fx 提供两种钩子:启动钩子,也被称为 OnStart 钩子。Fx 按照追加的顺序运行这些钩子。关闭钩子,也被称为 OnStop 钩子。运行顺序与追加顺序相反通常,提供启动钩子的组件也提供相应的关闭钩子,以释放启动时申请的资源。Fx 使用硬超时强制运行这两种钩子。因此,钩子只有在需要调度工作时才可以阻塞。换言之,钩子不能因同步运行长时间运行的任务而阻塞钩子应该在后台协程中调度长时间运行的任务关闭钩子应该关闭启动钩子启动的后台工作3.2. 模块Fx 模块是可共享的 Go 库或包,向 Fx 应用程序提供自包含(Self-Contained)的功能。3.2.1. 编写模块为编写 Fx 模块:定义顶级的 Module 变量(通过 fx.Module 调用构建)。给模块起一个简短的、容易记住的日志名称。xxxxxxxxxxvar Module = fx.Module("server",使用 fx.Provide 添加模块的组件。xxxxxxxxxxvar Module = fx.Module("server", fx.Provide( New, parseConfig, ),)如果模块拥有必须运行的函数,那么为其添加 fx.Invoke。xxxxxxxxxxvar Module = fx.Module("server", fx.Provide( New, parseConfig, ), fx.Invoke(startServer),)如果模块在使用其依赖之前需要装饰它们,那么为其添加 fx.Decorate 调用。xxxxxxxxxxvar Module = fx.Module("server", fx.Provide( New, parseConfig, ), fx.Invoke(startServer), fx.Decorate(wrapLogger),)最后,如果希望将构造器的输出保留在模块(以及模块包含的模块)中,那么在提供时添加 fx.Private。xxxxxxxxxxvar Module = fx.Module("server", fx.Provide( New, ), fx.Provide( fx.Private, parseConfig, ), fx.Invoke(startServer), fx.Decorate(wrapLogger),)在这种情况下,parseConfig 是“server”模块私有的。任何包含“server”的模块都不能使用结果 Config 类型,因为它仅对“server”模块可见。以上是关于编写模块的全部内容。本节的其余部分涵盖 Uber 为编写 Fx 模块建立的标准和惯例。3.2.1.1. 命名3.2.1.1.1. 包独立的 Fx 模块,即那些作为独立库分发的模块,或者那些在库中有独立 Go 包的模块,应该以它们包装的库或者提供的功能命名,并且添加“fx”后缀。BadGoodpackage mylibpackage mylibfxpackage httputilpackage httputilfx如果 Fx 模块是另一个 Go 包的一部分,或者是为特定应用程序编写的单服务模块,那么可以省略该后缀。3.2.1.1.2. 参数和结果对象参数和结果对象类型应该以它们对应的函数命名,命名方式是在函数名后添加 Params 或 Result 后缀。例外:如果函数名以 New 开头,那么在添加 Params 或 Result 后缀前,去掉 New 前缀。函数参数对象结果对象NewParamsResultRunRunParamsRunResultNewFooFooParamsFooResult3.2.1.2. 导出边界函数如果功能无法通过其它方式访问,那么导出模块通过 fx.Provide 或 fx.Invoke 使用的函数。xxxxxxxxxxvar Module = fx.Module("server", fx.Provide( New, parseConfig, ),)
type Config struct { Addr string `yaml:"addr"`}
func New(p Params) (Result, error) {在本例中,不导出 parseConfig,因为它只是简单的 yaml.Decode,无需暴露,但仍然导出 Config,以便用户可以自己解码。基本原则:应该可以在不使用 Fx 的情况下,使用 Fx 模块。用户应该可以直接调用构造器,获取与使用 Fx 时模块提供的相同功能。这对于紧急情况和部分迁移是必要的。Bad case:不使用 Fx,无法构建 serverxxxxxxxxxxvar Module = fx.Module("server",fx.Provide(newServer),)
func newServer(...) (*Server, error)3.2.1.3. 使用参数对象由模块暴露的函数不应该直接接受依赖项作为参数。而应该使用参数对象。xxxxxxxxxxtype Params struct { fx.In
Log *zap.Logger Config Config}
func New(p Params) (Result, error) {基本原则:模块不可避免地需要声明新依赖。通过使用参数对象,可以以向后兼容的方式添加新的可选依赖,并且无需更改函数签名。Bad case:无法在不破坏的情况下,添加新参数xxxxxxxxxxfunc New(log *zap.Logger) (Result, error)3.2.1.4. 使用结果对象由模块暴露的函数不应该将其结果声明为常规返回值。而应该使用结果对象。xxxxxxxxxxtype Result struct { fx.Out
Server *Server}
func New(p Params) (Result, error) {基本原则:模块将不可避免地需要返回新结果。通过使用结果对象,可以以向后兼容的方式生成新结果,并且无需更改函数签名。Bad case:无法在不破坏的情况下,添加新结果xxxxxxxxxxfunc New(Params) (*Server, error)3.2.1.5. 不要提供你不拥有的东西Fx 模块应该只向应用程序提供在其权限范围内的类型。模块不应该向应用程序提供它们碰巧使用的值。模块也不应该大量捆绑其它模块。基本原则:这让使用者可以自由地选择依赖的来源和方式。他们可以使用你推荐的方法(比如,“include zapfix.module”),或者构建该依赖的变体。Bad case:提供依赖xxxxxxxxxxpackage httpfx
type Result struct { fx.Out
Client *http.Client Logger *zap.Logger // BAD}Bad case:绑定其它模块xxxxxxxxxxpackage httpfx
var Module = fx.Module("http", fx.Provide(New), zapfx.Module, // BAD)例外:组织或团队级别的“kitchen sink”模块专门用于捆绑其它模块,可以忽略该规则。比如,Uber 的 uberfx.Module 模块捆绑若干其它独立模块。该模块中的所有东西都被所有服务所需要。3.2.1.6. 保持独立模块精简独立的 Fx 模块 -- 名称以“fx”结尾的模块很少包含重要的业务逻辑。如果 Fx 模块位于包含重要业务逻辑的包中,那么其名称中不应该有“fx”后缀。基本原则:在不重写业务逻辑的情况下,使用方应该可以迁移到或者离开 Fx。Good case:业务逻辑使用 net/http.Clientxxxxxxxxxxpackage httpfx
import "net/http"
type Result struct { fx.Out
Client *http.Client}Bad case:Fx 模块实现 Loggerxxxxxxxxxxpackage logfx
type Logger struct {// ...}
func New(...) Logger3.2.1.7. 谨慎地使用 Invoke当选择在模块中使用 fx.Invoke 时要谨慎。根据设计,Fx 仅在应用程序通过另一个模块、构造器或 Invoke 直接或间接地使用构造器的结果时,才执行通过 fx.Provide 添加的构造器。另一方面,Fx 无条件地运行使用 fx.Invoke 添加的函数,并且在这样做时,实例化其依赖的每个直接值和传递值。4. 特性4.1. 参数对象参数对象是仅用于携带特定函数或方法的参数的对象。通常专门为函数定义参数对象,并且不与其它函数共享。参数对象不是像“user”那样的通用对象,而是专门构建的对象,比如“GetUser 函数的参数”。在 Fx 中,参数对象只包含导出字段,并且始终附带 fx.In 标签。4.1.1. 使用参数对象为在 Fx 中使用参数对象,执行以下步骤:定义新结构体类型,命名为构造器名称加上 Params 后缀。如果构造器名称为 NewClient,那么将结构体命名为 ClientParams。如果构造器名称为 New,那么将结构体命名为 Params。该命名不是必须的,但是是很好的约定。xxxxxxxxxxtype ClientParams struct {}将 fx.In 嵌进结构体。xxxxxxxxxxtype ClientParams struct { fx.In按值将该新类型当作参数添加到构造器中。xxxxxxxxxxfunc NewClient(p ClientParams) (*Client, error) {将构造器的依赖项添加为该结构体的导出字段。xxxxxxxxxxtype ClientParams struct { fx.In
Config ClientConfig HTTPClient *http.Client}在构造器中使用这些字段。xxxxxxxxxxfunc NewClient(p ClientParams) (*Client, error) { return &Client{ url: p.Config.URL, http: p.HTTPClient, // ... }, nil4.1.2. 添加新参数可以通过向参数对象添加新字段的方式,为构造器添加新参数。为向后兼容,新字段必须是可选的。接受现有的参数对象。xxxxxxxxxxtype Params struct { fx.In
Config ClientConfig HTTPClient *http.Client}
func New(p Params) (*Client, error) {为新依赖,向参数对象中添加新字段,并且将其标记为可选的,以保证此改动向后兼容。xxxxxxxxxxtype Params struct { fx.In
Config ClientConfig HTTPClient *http.Client Logger *zap.Logger `optional:"true"`}在构造器中,使用该字段。确保处理缺少该字段的情况 - 在该情况下,将取其类型的零值。xxxxxxxxxxfunc New(p Params) (*Client, error) { log := p.Logger if log == nil { log = zap.NewNop() } // ...4.2. 结果对象结果对象是仅用于携带特定函数或方法的结果的对象。与参数对象一样,专门为单个函数定义结果对象,并且不与其它函数共享。在 Fx 中,结果对象只包含导出字段,并且始终附带 fx.Out 标签。4.2.1. 使用结果对象为在 Fx 中使用结果对象,执行以下步骤:定义新结构体类型,命名为构造器名称加上 Result 后缀。如果构造器名称为 NewClient,那么将结构体命名为 ClientResult。如果构造器名称为 New,那么将结构体命名为 Result。该命名不是必须的,但是是很好的约定。xxxxxxxxxxtype ClientResult struct {}将 fx.Out 嵌进结构体。xxxxxxxxxxtype ClientResult struct { fx.Out按值将该新类型用作构造器的返回值。xxxxxxxxxxfunc NewClient() (ClientResult, error) {将构造器生成的值添加为该结构体上的导出字段。xxxxxxxxxxtype ClientResult struct { fx.Out
Client *Client}在构造器中,设置这些字段,并且返回该结构体的实例。xxxxxxxxxxfunc NewClient() (ClientResult, error) { client := &Client{ // ... } return ClientResult{Client: client}, nil}4.2.2. 添加新结果可以以完全向后兼容的方式,向现有结果对象添加新值。接受现有结果对象。xxxxxxxxxxtype Result struct { fx.Out
Client *Client}
func New() (Result, error) { client := &Client{ // ... } return Result{ Client: client, }, nil为新结果,向结果对象中添加新字段。xxxxxxxxxxtype Result struct { fx.Out
Client *Client Inspector *Inspector}在构造器中,设置该字段。xxxxxxxxxx return Result{ Client: client, Inspector: &Inspector{ // ... }, }, nil4.3. 注解(Annotation)可以在将函数和值传递给 fx.Provide、fx.Supply、fx.Invoke、fx.Decorate 或 fx.Replace 之前,使用 fx.Annotate 函数对其进行注解。4.3.1. 对函数进行注解先决条件函数需要满足:当使用 fx.ParamTags 进行注解时,不接受参数对象当使用 fx.ResultTags 进行注解时,不返回结果对象步骤给定传递给 fx.Provide、fx.Invoke 或 fx.Decorate 的函数。xxxxxxxxxx fx.Provide( NewHTTPClient, ),使用 fx.Annotate 包装函数。xxxxxxxxxx fx.Provide( fx.Annotate( NewHTTPClient, ), ),在 fx.Annotate 内部,传入注解。xxxxxxxxxx fx.Provide( fx.Annotate( NewHTTPClient, fx.ResultTags(`name:"client"`), ), ),该注解使用名称标记函数的结果。4.3.2. 将结构体强制转换为接口可以使用函数注解将函数返回的结构体值强制转换为其它函数使用的接口。先决条件生成结构体或指针值的函数。xxxxxxxxxxfunc NewHTTPClient(Config) (*http.Client, error) {使用生产者结果的函数。xxxxxxxxxxfunc NewGitHubClient(client *http.Client) *github.Client {将这两个函数提供给 Fx 应用程序。xxxxxxxxxx fx.Provide( NewHTTPClient, NewGitHubClient, ),步骤声明匹配 *http.Client 的 API 的接口。xxxxxxxxxxtype HTTPClient interface { Do(*http.Request) (*http.Response, error)}
// 下面的语句在编译时检查接口是否匹配 http.Client 的 API。var _ HTTPClient = (*http.Client)(nil)修改使用者,使其接受接口,而非结构体。xxxxxxxxxxfunc NewGitHubClient(client HTTPClient) *github.Client {最后,使用 fx.As 对生产者进行注解,说明它生成接口值。xxxxxxxxxx fx.Provide( fx.Annotate( NewHTTPClient, fx.As(new(HTTPClient)), ), NewGitHubClient, ),使用该改动:被注解的函数仅将接口放进容器生产者的 API 保持不变使用者与实现解耦,可以独立测试4.4. 值组(Value Group)值组是相同类型的值的集合。Fx 应用程序中的任何构造器都可以向值组提供值。类似地,任何使用者都可以在不知道生产者完整列表的情况下从值组中读取值。提示Fx 生成的值以随机顺序提供给值组。不要对值组顺序做任何假设。4.4.1. 使用值组4.4.2. 依赖严格性通过值组形成的依赖关系可以是:严格的:始终被使用软的:仅当在其它地方请求相应的构造器时,才被使用默认情况下,值组的赖是严格的。4.4.2.1. 严格的值组无论生产者是否被应用程序使用,值组都使用严格的值组依赖。假设构造器 NewFoo 生成两个值:A 和 B。将值 A 提供给值组 []A,函数 Run 使用该值组,应用程序使用 fx.Invoke 调用函数 Run。对于严格的值组,Fx 将运行 NewFoo,填充 []A 组,而不管应用程序是否直接或间接地使用其它结果(B)。4.4.2.2. 软值组仅当生成软值组依赖的构造器被 Fx 调用时,值组才使用软值组依赖 -- 因为应用程序直接或间接地使用它们的其它结果。下面的组织结构除值组是软的外,与前一节类似。对于软值组,Fx 仅在 A 或 B 被应用程序中的另一个组件直接或间接地使用时,才运行 NewFoo 填充 []A 组。4.4.3. 向值组提供值为向类型为 T 的值组提供值,必须使用 group:"$name" 标记 T 结果,其中 $name 是值组名称。可以通过如下方式实现:使用结果对象使用带注解的函数4.4.3.1. 使用结果对象可以使用结果对象标记函数的结果,并且将其提供给值组。先决条件生成结果对象的函数。xxxxxxxxxxtype Result struct { fx.Out
// ...}
func New( /* ... */ ) (Result, error) { // ... return Result{ // ... Watcher: watcher, }, nil}将函数提供给 Fx 应用程序。xxxxxxxxxx fx.Provide(New),步骤使用想要生成的值的类型向结果对象添加新导出字段,并且用值组的名称标记该字段。xxxxxxxxxxtype Result struct { fx.Out
// ... Watcher Watcher `group:"watchers"`}在函数中,将该字段设置为想要提供给值组的值。xxxxxxxxxxfunc New( /* ... */ ) (Result, error) { // ... watcher := &watcher{ // ... }
return Result{ // ... Watcher: watcher, }, nil}4.4.3.2. 使用带注解的函数可以使用注解向值组发送函数的结果。先决条件生成值组所需类型的值的函数。xxxxxxxxxxfunc NewWatcher( /* ... */ ) (Watcher, error) { // ...将函数提供给 Fx 应用程序。xxxxxxxxxx fx.Provide( NewWatcher, ),步骤使用 fx.Annotate 包装传递给 fx.Provide 的函数。xxxxxxxxxx fx.Provide( fx.Annotate( NewWatcher, ), ),对该函数进行注解,以说明将其结果提供给值组。xxxxxxxxxx fx.Annotate( NewWatcher, fx.ResultTags(`group:"watchers"`), )提示:类型无需必须匹配如果注解的函数不生成与组相同的类型,那么可以将其强制转换为该类型:xxxxxxxxxxfunc NewFileWatcher( /* ... */ ) (*FileWatcher, error) {仍然可以使用注解将其提供给值组。xxxxxxxxxx fx.Annotate( NewFileWatcher, fx.As(new(Watcher)), fx.ResultTags(`group:"watchers"`), ),4.4.4. 从值组获取值为使用类型为 T 的值组,必须使用 group:"$name" 标记 []T 依赖,其中 $name 是值组的名称。可以通过如下方式实现:使用参数对象使用带注解的函数4.4.4.1. 使用参数对象可以使用参数对象将函数的切片参数标记为值组。先决条件使用参数对象的函数。xxxxxxxxxxtype Params struct { fx.In
// ...}
func New(p Params) (Result, error) { // ...将函数提供给 Fx 应用程序。xxxxxxxxxx fx.Provide(New),步骤向参数对象添加类型为 []T 的新导出字段,其中 T 是值组中值的种类。使用值组名称标记该字段。xxxxxxxxxxtype Params struct { fx.In
// ... Watchers []Watcher `group:"watchers"`}在接收该参数对象的函数中使用该切片。xxxxxxxxxxfunc New(p Params) (Result, error) { // ... for _, w := range p.Watchers { // ...警告不要依赖切片里的值的顺序。因为顺序是随机的。4.4.4.2. 使用带注解的函数可以使用注解从函数中使用值组。先决条件接受组中的值类型的切片的函数。xxxxxxxxxxfunc NewEmitter(watchers []Watcher) (*Emitter, error) {将函数提供给 Fx 应用程序。xxxxxxxxxx fx.Provide( NewEmitter, ),步骤使用 fx.Annotate 包装传递给 fx.Provide 的函数。xxxxxxxxxx fx.Provide( fx.Annotate( NewEmitter, ), ),对该函数进行注解,说明其切片参数为值组。xxxxxxxxxx fx.Annotate( NewEmitter, fx.ParamTags(`group:"watchers"`), ),在函数中使用该切片。xxxxxxxxxxfunc NewEmitter(watchers []Watcher) (*Emitter, error) { for _, w := range watchers { // ...提示:函数可以接受变长参数可以在接受变长参数的函数中使用值组。xxxxxxxxxxfunc EmitterFrom(watchers ...Watcher) (*Emitter, error) {return &Emitter{ws: watchers}, nil}对变长参数进行注解。xxxxxxxxxx fx.Annotate( EmitterFrom, fx.ParamTags(`group:"watchers"`), ),
fx package - go.uber.org/fx - Go Packages
fx package - go.uber.org/fx - Go Packages
Skip to Main Content
Why Go
Case Studies
Common problems companies solve with Go
Use Cases
Stories about how and why companies use Go
Security Policy
How Go can help keep you secure by default
Learn
Docs
Effective Go
Tips for writing clear, performant, and idiomatic Go code
Go User Manual
A complete introduction to building software with Go
Standard library
Reference documentation for Go's standard library
Release Notes
Learn what's new in each Go release
Packages
Community
Recorded Talks
Videos from prior events
Meetups
Meet other local Go developers
Conferences
Learn and network with Go developers from around the world
Go blog
The Go project's official blog.
Go project
Get help and stay informed from Go
Get connected
Why Go
Why Go
Case Studies
Use Cases
Security Policy
Learn
Docs
Docs
Effective Go
Go User Manual
Standard library
Release Notes
Packages
Community
Community
Recorded Talks
Meetups
Conferences
Go blog
Go project
Get connected
Discover Packages
go.uber.org/fx
fx
package
module
Version:
v1.20.1
Opens a new window with list of versions in this module.
Latest
Latest
This package is not in the latest version of its module.
Go to latest
Published: Oct 17, 2023
License: MIT
Opens a new window with license information.
Imports: 21
Opens a new window with list of imports.
Imported by: 4,263
Opens a new window with list of known importers.
Main
Versions
Licenses
Imports
Imported By
Details
Valid go.mod file
The Go module system was introduced in Go 1.11 and is the official dependency management
solution for Go.
Redistributable license
Redistributable licenses place minimal restrictions on how software can be used,
modified, and redistributed.
Tagged version
Modules with tagged versions give importers more predictable builds.
Stable version
When a project reaches major version v1 it is considered stable.
Learn more about best practices
Repository
github.com/uber-go/fx
Links
Open Source Insights
Jump to ...
README
Installation
Getting started
Stability
Stargazers over time
Documentation
Overview
Index
Examples
Constants
Variables
Functions
Annotate(t, anns)
ValidateApp(opts)
VisualizeError(err)
Types
type Annotated
(a) String()
type Annotation
As(interfaces)
From(interfaces)
OnStart(onStart)
OnStop(onStop)
ParamTags(tags)
ResultTags(tags)
type App
New(opts)
(app) Done()
(app) Err()
(app) Run()
(app) Start(ctx)
(app) StartTimeout()
(app) Stop(ctx)
(app) StopTimeout()
(app) Wait()
type DotGraph
type ErrorHandler
type Hook
StartHook(start)
StartStopHook(start, stop)
StopHook(stop)
type HookFunc
type In
type Lifecycle
type Option
Decorate(decorators)
Error(errs)
ErrorHook(funcs)
Extract(target)
Invoke(funcs)
Logger(p)
Module(name, opts)
Options(opts)
Populate(targets)
Provide(constructors)
RecoverFromPanics()
Replace(values)
StartTimeout(v)
StopTimeout(v)
Supply(values)
WithLogger(constructor)
type Out
type Printer
type ShutdownOption
ExitCode(code)
ShutdownTimeout(timeout)
type ShutdownSignal
(sig) String()
type Shutdowner
Source Files
Directories
README
README
¶
Fx
Fx is a dependency injection system for Go.
Benefits
Eliminate globals: Fx helps you remove global state from your application.
No more init() or global variables. Use Fx-managed singletons.
Code reuse: Fx lets teams within your organization build loosely-coupled
and well-integrated shareable components.
Battle tested: Fx is the backbone of nearly all Go services at Uber.
See our docs to get started and/or
learn more about Fx.
Installation
Use Go modules to install Fx in your application.
go get go.uber.org/fx@v1
Getting started
To get started with Fx, start here.
Stability
This library is v1 and follows SemVer strictly.
No breaking changes will be made to exported APIs before v2.0.0.
This project follows the Go Release Policy. Each major
version of Go is supported until there are two newer major releases.
Stargazers over time
Expand ▾
Collapse ▴
Documentation
¶
Rendered for
linux/amd64
windows/amd64
darwin/amd64
js/wasm
Overview ¶
Testing Fx Applications
Package fx is a framework that makes it easy to build applications out of
reusable, composable modules.
Fx applications use dependency injection to eliminate globals without the
tedium of manually wiring together function calls. Unlike other approaches
to dependency injection, Fx works with plain Go functions: you don't need
to use struct tags or embed special types, so Fx automatically works well
with most Go packages.
Basic usage is explained in the package-level example below. If you're new
to Fx, start there! Advanced features, including named instances, optional
parameters, and value groups, are explained under the In and Out types.
Testing Fx Applications ¶To test functions that use the Lifecycle type or to write end-to-end tests
of your Fx application, use the helper functions and types provided by the
go.uber.org/fx/fxtest package.
Example ¶
package main
import (
"context"
"log"
"net"
"net/http"
"os"
"time"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
)
// NewLogger constructs a logger. It's just a regular Go function, without any
// special relationship to Fx.
//
// Since it returns a *log.Logger, Fx will treat NewLogger as the constructor
// function for the standard library's logger. (We'll see how to integrate
// NewLogger into an Fx application in the main function.) Since NewLogger
// doesn't have any parameters, Fx will infer that loggers don't depend on any
// other types - we can create them from thin air.
//
// Fx calls constructors lazily, so NewLogger will only be called only if some
// other function needs a logger. Once instantiated, the logger is cached and
// reused - within the application, it's effectively a singleton.
//
// By default, Fx applications only allow one constructor for each type. See
// the documentation of the In and Out types for ways around this restriction.
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
// NewHandler constructs a simple HTTP handler. Since it returns an
// http.Handler, Fx will treat NewHandler as the constructor for the
// http.Handler type.
//
// Like many Go functions, NewHandler also returns an error. If the error is
// non-nil, Go convention tells the caller to assume that NewHandler failed
// and the other returned values aren't safe to use. Fx understands this
// idiom, and assumes that any function whose last return value is an error
// follows this convention.
//
// Unlike NewLogger, NewHandler has formal parameters. Fx will interpret these
// parameters as dependencies: in order to construct an HTTP handler,
// NewHandler needs a logger. If the application has access to a *log.Logger
// constructor (like NewLogger above), it will use that constructor or its
// cached output and supply a logger to NewHandler. If the application doesn't
// know how to construct a logger and needs an HTTP handler, it will fail to
// start.
//
// Functions may also return multiple objects. For example, we could combine
// NewHandler and NewLogger into a single function:
//
// func NewHandlerAndLogger() (*log.Logger, http.Handler, error)
//
// Fx also understands this idiom, and would treat NewHandlerAndLogger as the
// constructor for both the *log.Logger and http.Handler types. Just like
// constructors for a single type, NewHandlerAndLogger would be called at most
// once, and both the handler and the logger would be cached and reused as
// necessary.
func NewHandler(logger *log.Logger) (http.Handler, error) {
logger.Print("Executing NewHandler.")
return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
logger.Print("Got a request.")
}), nil
}
// NewMux constructs an HTTP mux. Like NewHandler, it depends on *log.Logger.
// However, it also depends on the Fx-specific Lifecycle interface.
//
// A Lifecycle is available in every Fx application. It lets objects hook into
// the application's start and stop phases. In a non-Fx application, the main
// function often includes blocks like this:
//
// srv, err := NewServer() // some long-running network server
// if err != nil {
// log.Fatalf("failed to construct server: %v", err)
// }
// // Construct other objects as necessary.
// go srv.Start()
// defer srv.Stop()
//
// In this example, the programmer explicitly constructs a bunch of objects,
// crashing the program if any of the constructors encounter unrecoverable
// errors. Once all the objects are constructed, we start any background
// goroutines and defer cleanup functions.
//
// Fx removes the manual object construction with dependency injection. It
// replaces the inline goroutine spawning and deferred cleanups with the
// Lifecycle type.
//
// Here, NewMux makes an HTTP mux available to other functions. Since
// constructors are called lazily, we know that NewMux won't be called unless
// some other function wants to register a handler. This makes it easy to use
// Fx's Lifecycle to start an HTTP server only if we have handlers registered.
func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
logger.Print("Executing NewMux.")
// First, we construct the mux and server. We don't want to start the server
// until all handlers are registered.
mux := http.NewServeMux()
server := &http.Server{
Addr: "127.0.0.1:8080",
Handler: mux,
}
// If NewMux is called, we know that another function is using the mux. In
// that case, we'll use the Lifecycle type to register a Hook that starts
// and stops our HTTP server.
//
// Hooks are executed in dependency order. At startup, NewLogger's hooks run
// before NewMux's. On shutdown, the order is reversed.
//
// Returning an error from OnStart hooks interrupts application startup. Fx
// immediately runs the OnStop portions of any successfully-executed OnStart
// hooks (so that types which started cleanly can also shut down cleanly),
// then exits.
//
// Returning an error from OnStop hooks logs a warning, but Fx continues to
// run the remaining hooks.
lc.Append(fx.Hook{
// To mitigate the impact of deadlocks in application startup and
// shutdown, Fx imposes a time limit on OnStart and OnStop hooks. By
// default, hooks have a total of 15 seconds to complete. Timeouts are
// passed via Go's usual context.Context.
OnStart: func(context.Context) error {
logger.Print("Starting HTTP server.")
ln, err := net.Listen("tcp", server.Addr)
if err != nil {
return err
}
go server.Serve(ln)
return nil
},
OnStop: func(ctx context.Context) error {
logger.Print("Stopping HTTP server.")
return server.Shutdown(ctx)
},
})
return mux
}
// Register mounts our HTTP handler on the mux.
//
// Register is a typical top-level application function: it takes a generic
// type like ServeMux, which typically comes from a third-party library, and
// introduces it to a type that contains our application logic. In this case,
// that introduction consists of registering an HTTP handler. Other typical
// examples include registering RPC procedures and starting queue consumers.
//
// Fx calls these functions invocations, and they're treated differently from
// the constructor functions above. Their arguments are still supplied via
// dependency injection and they may still return an error to indicate
// failure, but any other return values are ignored.
//
// Unlike constructors, invocations are called eagerly. See the main function
// below for details.
func Register(mux *http.ServeMux, h http.Handler) {
mux.Handle("/", h)
}
func main() {
app := fx.New(
// Provide all the constructors we need, which teaches Fx how we'd like to
// construct the *log.Logger, http.Handler, and *http.ServeMux types.
// Remember that constructors are called lazily, so this block doesn't do
// much on its own.
fx.Provide(
NewLogger,
NewHandler,
NewMux,
),
// Since constructors are called lazily, we need some invocations to
// kick-start our application. In this case, we'll use Register. Since it
// depends on an http.Handler and *http.ServeMux, calling it requires Fx
// to build those types using the constructors above. Since we call
// NewMux, we also register Lifecycle hooks to start and stop an HTTP
// server.
fx.Invoke(Register),
// This is optional. With this, you can control where Fx logs
// its events. In this case, we're using a NopLogger to keep
// our test silent. Normally, you'll want to use an
// fxevent.ZapLogger or an fxevent.ConsoleLogger.
fx.WithLogger(
func() fxevent.Logger {
return fxevent.NopLogger
},
),
)
// In a typical application, we could just use app.Run() here. Since we
// don't want this example to run forever, we'll use the more-explicit Start
// and Stop.
startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil {
log.Fatal(err)
}
// Normally, we'd block here with <-app.Done(). Instead, we'll make an HTTP
// request to demonstrate that our server is running.
if _, err := http.Get("http://localhost:8080/"); err != nil {
log.Fatal(err)
}
stopCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Stop(stopCtx); err != nil {
log.Fatal(err)
}
}
Output:
Executing NewLogger.
Executing NewMux.
Executing NewHandler.
Starting HTTP server.
Got a request.
Stopping HTTP server.
Share
Format
Run
Index ¶
Constants
Variables
func Annotate(t interface{}, anns ...Annotation) interface{}
func ValidateApp(opts ...Option) error
func VisualizeError(err error) (string, error)
type Annotated
func (a Annotated) String() string
type Annotation
func As(interfaces ...interface{}) Annotation
func From(interfaces ...interface{}) Annotation
func OnStart(onStart interface{}) Annotation
func OnStop(onStop interface{}) Annotation
func ParamTags(tags ...string) Annotation
func ResultTags(tags ...string) Annotation
type App
func New(opts ...Option) *App
func (app *App) Done() <-chan os.Signal
func (app *App) Err() error
func (app *App) Run()
func (app *App) Start(ctx context.Context) (err error)
func (app *App) StartTimeout() time.Duration
func (app *App) Stop(ctx context.Context) (err error)
func (app *App) StopTimeout() time.Duration
func (app *App) Wait() <-chan ShutdownSignal
type DotGraph
type ErrorHandler
type Hook
func StartHook[T HookFunc](start T) Hook
func StartStopHook[T1 HookFunc, T2 HookFunc](start T1, stop T2) Hook
func StopHook[T HookFunc](stop T) Hook
type HookFunc
type In
type Lifecycle
type Option
func Decorate(decorators ...interface{}) Option
func Error(errs ...error) Option
func ErrorHook(funcs ...ErrorHandler) Option
func Extract(target interface{}) Optiondeprecated
func Invoke(funcs ...interface{}) Option
func Logger(p Printer) Option
func Module(name string, opts ...Option) Option
func Options(opts ...Option) Option
func Populate(targets ...interface{}) Option
func Provide(constructors ...interface{}) Option
func RecoverFromPanics() Option
func Replace(values ...interface{}) Option
func StartTimeout(v time.Duration) Option
func StopTimeout(v time.Duration) Option
func Supply(values ...interface{}) Option
func WithLogger(constructor interface{}) Option
type Out
type Printer
type ShutdownOption
func ExitCode(code int) ShutdownOption
func ShutdownTimeout(timeout time.Duration) ShutdownOptiondeprecated
type ShutdownSignal
func (sig ShutdownSignal) String() string
type Shutdowner
Examples ¶
Package
Error
Populate
Constants ¶
View Source
const DefaultTimeout = 15 * time.Second
DefaultTimeout is the default timeout for starting or stopping an
application. It can be configured with the StartTimeout and StopTimeout
options.
View Source
const Version = "1.20.1"
Version is exported for runtime compatibility checks.
Variables ¶
View Source
var NopLogger = WithLogger(func() fxevent.Logger { return fxevent.NopLogger })
NopLogger disables the application's log output. Note that this makes some
failures difficult to debug, since no errors are printed to console.
View Source
var Private = privateOption{}
Private is an option that can be passed as an argument to Provide to
restrict access to the constructors being provided. Specifically,
corresponding constructors can only be used within the current module
or modules the current module contains. Other modules that contain this
module won't be able to use the constructor.
For example, the following would fail because the app doesn't have access
to the inner module's constructor.
fx.New(
fx.Module("SubModule", fx.Provide(func() int { return 0 }, fx.Private)),
fx.Invoke(func(a int) {}),
)
Functions ¶
func Annotate ¶
added in
v1.15.0
func Annotate(t interface{}, anns ...Annotation) interface{}
Variadic functions
Annotate lets you annotate a function's parameters and returns
without you having to declare separate struct definitions for them.
For example,
func NewGateway(ro, rw *db.Conn) *Gateway { ... }
fx.Provide(
fx.Annotate(
NewGateway,
fx.ParamTags(`name:"ro" optional:"true"`, `name:"rw"`),
fx.ResultTags(`name:"foo"`),
),
)
Is equivalent to,
type params struct {
fx.In
RO *db.Conn `name:"ro" optional:"true"`
RW *db.Conn `name:"rw"`
}
type result struct {
fx.Out
GW *Gateway `name:"foo"`
}
fx.Provide(func(p params) result {
return result{GW: NewGateway(p.RO, p.RW)}
})
Using the same annotation multiple times is invalid.
For example, the following will fail with an error:
fx.Provide(
fx.Annotate(
NewGateWay,
fx.ParamTags(`name:"ro" optional:"true"`),
fx.ParamTags(`name:"rw"), // ERROR: ParamTags was already used above
fx.ResultTags(`name:"foo"`)
)
)
is considered an invalid usage and will not apply any of the
Annotations to NewGateway.
If more tags are given than the number of parameters/results, only
the ones up to the number of parameters/results will be applied.
Variadic functions ¶If the provided function is variadic, Annotate treats its parameter as a
slice. For example,
fx.Annotate(func(w io.Writer, rs ...io.Reader) {
// ...
}, ...)
Is equivalent to,
fx.Annotate(func(w io.Writer, rs []io.Reader) {
// ...
}, ...)
You can use variadic parameters with Fx's value groups.
For example,
fx.Annotate(func(mux *http.ServeMux, handlers ...http.Handler) {
// ...
}, fx.ParamTags(``, `group:"server"`))
If we provide the above to the application,
any constructor in the Fx application can inject its HTTP handlers
by using fx.Annotate, fx.Annotated, or fx.Out.
fx.Annotate(
func(..) http.Handler { ... },
fx.ResultTags(`group:"server"`),
)
fx.Annotated{
Target: func(..) http.Handler { ... },
Group: "server",
}
func ValidateApp ¶
added in
v1.13.0
func ValidateApp(opts ...Option) error
ValidateApp validates that supplied graph would run and is not missing any dependencies. This
method does not invoke actual input functions.
func VisualizeError ¶
added in
v1.7.0
func VisualizeError(err error) (string, error)
VisualizeError returns the visualization of the error if available.
Types ¶
type Annotated ¶
added in
v1.9.0
type Annotated struct {
// If specified, this will be used as the name for all non-error values returned
// by the constructor. For more information on named values, see the documentation
// for the fx.Out type.
//
// A name option may not be provided if a group option is provided.
Name string
// If specified, this will be used as the group name for all non-error values returned
// by the constructor. For more information on value groups, see the package documentation.
//
// A group option may not be provided if a name option is provided.
//
// Similar to group tags, the group name may be followed by a `,flatten`
// option to indicate that each element in the slice returned by the
// constructor should be injected into the value group individually.
Group string
// Target is the constructor or value being annotated with fx.Annotated.
Target interface{}
}
Annotated annotates a constructor provided to Fx with additional options.
For example,
func NewReadOnlyConnection(...) (*Connection, error)
fx.Provide(fx.Annotated{
Name: "ro",
Target: NewReadOnlyConnection,
})
Is equivalent to,
type result struct {
fx.Out
Connection *Connection `name:"ro"`
}
fx.Provide(func(...) (result, error) {
conn, err := NewReadOnlyConnection(...)
return result{Connection: conn}, err
})
Annotated cannot be used with constructors which produce fx.Out objects.
When used with fx.Supply, the target is a value rather than a constructor function.
func (Annotated) String ¶
added in
v1.10.0
func (a Annotated) String() string
type Annotation ¶
added in
v1.15.0
type Annotation interface {
// contains filtered or unexported methods
}
Annotation can be passed to Annotate(f interface{}, anns ...Annotation)
for annotating the parameter and result types of a function.
func As ¶
added in
v1.15.0
func As(interfaces ...interface{}) Annotation
As is an Annotation that annotates the result of a function (i.e. a
constructor) to be provided as another interface.
For example, the following code specifies that the return type of
bytes.NewBuffer (bytes.Buffer) should be provided as io.Writer type:
fx.Provide(
fx.Annotate(bytes.NewBuffer(...), fx.As(new(io.Writer)))
)
In other words, the code above is equivalent to:
fx.Provide(func() io.Writer {
return bytes.NewBuffer()
// provides io.Writer instead of *bytes.Buffer
})
Note that the bytes.Buffer type is provided as an io.Writer type, so this
constructor does NOT provide both bytes.Buffer and io.Writer type; it just
provides io.Writer type.
When multiple values are returned by the annotated function, each type
gets mapped to corresponding positional result of the annotated function.
For example,
func a() (bytes.Buffer, bytes.Buffer) {
...
}
fx.Provide(
fx.Annotate(a, fx.As(new(io.Writer), new(io.Reader)))
)
Is equivalent to,
fx.Provide(func() (io.Writer, io.Reader) {
w, r := a()
return w, r
}
As annotation cannot be used in a function that returns an Out struct as a return type.
func From ¶
added in
v1.19.0
func From(interfaces ...interface{}) Annotation
From is an Annotation that annotates the parameter(s) for a function (i.e. a
constructor) to be accepted from other provided types. It is analogous to the
As for parameter types to the constructor.
For example,
type Runner interface { Run() }
func NewFooRunner() *FooRunner // implements Runner
func NewRunnerWrap(r Runner) *RunnerWrap
fx.Provide(
fx.Annotate(
NewRunnerWrap,
fx.From(new(*FooRunner)),
),
)
Is equivalent to,
fx.Provide(func(r *FooRunner) *RunnerWrap {
// need *FooRunner instead of Runner
return NewRunnerWrap(r)
})
When the annotated function takes in multiple parameters, each type gets
mapped to corresponding positional parameter of the annotated function
For example,
func NewBarRunner() *BarRunner // implements Runner
func NewRunnerWraps(r1 Runner, r2 Runner) *RunnerWraps
fx.Provide(
fx.Annotate(
NewRunnerWraps,
fx.From(new(*FooRunner), new(*BarRunner)),
),
)
Is equivalent to,
fx.Provide(func(r1 *FooRunner, r2 *BarRunner) *RunnerWraps {
return NewRunnerWraps(r1, r2)
})
From annotation cannot be used in a function that takes an In struct as a
parameter.
func OnStart ¶
added in
v1.18.0
func OnStart(onStart interface{}) Annotation
OnStart is an Annotation that appends an OnStart Hook to the application
Lifecycle when that function is called. This provides a way to create
Lifecycle OnStart (see Lifecycle type documentation) hooks without building a
function that takes a dependency on the Lifecycle type.
fx.Provide(
fx.Annotate(
NewServer,
fx.OnStart(func(ctx context.Context, server Server) error {
return server.Listen(ctx)
}),
)
)
Which is functionally the same as:
fx.Provide(
func(lifecycle fx.Lifecycle, p Params) Server {
server := NewServer(p)
lifecycle.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
return server.Listen(ctx)
},
})
return server
}
)
It is also possible to use OnStart annotation with other parameter and result
annotations, provided that the parameter of the function passed to OnStart
matches annotated parameters and results.
For example, the following is possible:
fx.Provide(
fx.Annotate(
func (a A) B {...},
fx.ParamTags(`name:"A"`),
fx.ResultTags(`name:"B"`),
fx.OnStart(func (p OnStartParams) {...}),
),
)
As long as OnStartParams looks like the following and has no other dependencies
besides Context or Lifecycle:
type OnStartParams struct {
fx.In
FieldA A `name:"A"`
FieldB B `name:"B"`
}
Only one OnStart annotation may be applied to a given function at a time,
however functions may be annotated with other types of lifecycle Hooks, such
as OnStop. The hook function passed into OnStart cannot take any arguments
outside of the annotated constructor's existing dependencies or results, except
a context.Context.
func OnStop ¶
added in
v1.18.0
func OnStop(onStop interface{}) Annotation
OnStop is an Annotation that appends an OnStop Hook to the application
Lifecycle when that function is called. This provides a way to create
Lifecycle OnStop (see Lifecycle type documentation) hooks without building a
function that takes a dependency on the Lifecycle type.
fx.Provide(
fx.Annotate(
NewServer,
fx.OnStop(func(ctx context.Context, server Server) error {
return server.Shutdown(ctx)
}),
)
)
Which is functionally the same as:
fx.Provide(
func(lifecycle fx.Lifecycle, p Params) Server {
server := NewServer(p)
lifecycle.Append(fx.Hook{
OnStop: func(ctx context.Context) error {
return server.Shutdown(ctx)
},
})
return server
}
)
It is also possible to use OnStop annotation with other parameter and result
annotations, provided that the parameter of the function passed to OnStop
matches annotated parameters and results.
For example, the following is possible:
fx.Provide(
fx.Annotate(
func (a A) B {...},
fx.ParamTags(`name:"A"`),
fx.ResultTags(`name:"B"`),
fx.OnStop(func (p OnStopParams) {...}),
),
)
As long as OnStopParams looks like the following and has no other dependencies
besides Context or Lifecycle:
type OnStopParams struct {
fx.In
FieldA A `name:"A"`
FieldB B `name:"B"`
}
Only one OnStop annotation may be applied to a given function at a time,
however functions may be annotated with other types of lifecycle Hooks, such
as OnStart. The hook function passed into OnStop cannot take any arguments
outside of the annotated constructor's existing dependencies or results, except
a context.Context.
func ParamTags ¶
added in
v1.15.0
func ParamTags(tags ...string) Annotation
ParamTags is an Annotation that annotates the parameter(s) of a function.
When multiple tags are specified, each tag is mapped to the corresponding
positional parameter.
ParamTags cannot be used in a function that takes an fx.In struct as a
parameter.
func ResultTags ¶
added in
v1.15.0
func ResultTags(tags ...string) Annotation
ResultTags is an Annotation that annotates the result(s) of a function.
When multiple tags are specified, each tag is mapped to the corresponding
positional result.
ResultTags cannot be used on a function that returns an fx.Out struct.
type App ¶
type App struct {
// contains filtered or unexported fields
}
An App is a modular application built around dependency injection. Most
users will only need to use the New constructor and the all-in-one Run
convenience method. In more unusual cases, users may need to use the Err,
Start, Done, and Stop methods by hand instead of relying on Run.
New creates and initializes an App. All applications begin with a
constructor for the Lifecycle type already registered.
In addition to that built-in functionality, users typically pass a handful
of Provide options and one or more Invoke options. The Provide options
teach the application how to instantiate a variety of types, and the Invoke
options describe how to initialize the application.
When created, the application immediately executes all the functions passed
via Invoke options. To supply these functions with the parameters they
need, the application looks for constructors that return the appropriate
types; if constructors for any required types are missing or any
invocations return an error, the application will fail to start (and Err
will return a descriptive error message).
Once all the invocations (and any required constructors) have been called,
New returns and the application is ready to be started using Run or Start.
On startup, it executes any OnStart hooks registered with its Lifecycle.
OnStart hooks are executed one at a time, in order, and must all complete
within a configurable deadline (by default, 15 seconds). For details on the
order in which OnStart hooks are executed, see the documentation for the
Start method.
At this point, the application has successfully started up. If started via
Run, it will continue operating until it receives a shutdown signal from
Done (see the Done documentation for details); if started explicitly via
Start, it will operate until the user calls Stop. On shutdown, OnStop hooks
execute one at a time, in reverse order, and must all complete within a
configurable deadline (again, 15 seconds by default).
func New ¶
func New(opts ...Option) *App
New creates and initializes an App, immediately executing any functions
registered via Invoke options. See the documentation of the App struct for
details on the application's initialization, startup, and shutdown logic.
func (*App) Done ¶
func (app *App) Done() <-chan os.Signal
Done returns a channel of signals to block on after starting the
application. Applications listen for the SIGINT and SIGTERM signals; during
development, users can send the application SIGTERM by pressing Ctrl-C in
the same terminal as the running process.
Alternatively, a signal can be broadcast to all done channels manually by
using the Shutdown functionality (see the Shutdowner documentation for details).
Note: The channel Done returns will not receive a signal unless the application
as been started via Start or Run.
func (*App) Err ¶
func (app *App) Err() error
Err returns any error encountered during New's initialization. See the
documentation of the New method for details, but typical errors include
missing constructors, circular dependencies, constructor errors, and
invocation errors.
Most users won't need to use this method, since both Run and Start
short-circuit if initialization failed.
func (*App) Run ¶
func (app *App) Run()
Run starts the application, blocks on the signals channel, and then
gracefully shuts the application down. It uses DefaultTimeout to set a
deadline for application startup and shutdown, unless the user has
configured different timeouts with the StartTimeout or StopTimeout options.
It's designed to make typical applications simple to run.
However, all of Run's functionality is implemented in terms of the exported
Start, Done, and Stop methods. Applications with more specialized needs
can use those methods directly instead of relying on Run.
func (*App) Start ¶
func (app *App) Start(ctx context.Context) (err error)
Start kicks off all long-running goroutines, like network servers or
message queue consumers. It does this by interacting with the application's
Lifecycle.
By taking a dependency on the Lifecycle type, some of the user-supplied
functions called during initialization may have registered start and stop
hooks. Because initialization calls constructors serially and in dependency
order, hooks are naturally registered in serial and dependency order too.
Start executes all OnStart hooks registered with the application's
Lifecycle, one at a time and in order. This ensures that each constructor's
start hooks aren't executed until all its dependencies' start hooks
complete. If any of the start hooks return an error, Start short-circuits,
calls Stop, and returns the inciting error.
Note that Start short-circuits immediately if the New constructor
encountered any errors in application initialization.
func (*App) StartTimeout ¶
added in
v1.5.0
func (app *App) StartTimeout() time.Duration
StartTimeout returns the configured startup timeout. Apps default to using
DefaultTimeout, but users can configure this behavior using the
StartTimeout option.
func (*App) Stop ¶
func (app *App) Stop(ctx context.Context) (err error)
Stop gracefully stops the application. It executes any registered OnStop
hooks in reverse order, so that each constructor's stop hooks are called
before its dependencies' stop hooks.
If the application didn't start cleanly, only hooks whose OnStart phase was
called are executed. However, all those hooks are executed, even if some
fail.
func (*App) StopTimeout ¶
added in
v1.5.0
func (app *App) StopTimeout() time.Duration
StopTimeout returns the configured shutdown timeout. Apps default to using
DefaultTimeout, but users can configure this behavior using the StopTimeout
option.
func (*App) Wait ¶
added in
v1.19.0
func (app *App) Wait() <-chan ShutdownSignal
Wait returns a channel of ShutdownSignal to block on after starting the
application and function, similar to App.Done, but with a minor difference.
Should an ExitCode be provided as a ShutdownOption to
the Shutdowner Shutdown method, the exit code will be available as part
of the ShutdownSignal struct.
Should the app receive a SIGTERM or SIGINT, the given
signal will be populated in the ShutdownSignal struct.
type DotGraph ¶
added in
v1.7.0
type DotGraph string
DotGraph contains a DOT language visualization of the dependency graph in
an Fx application. It is provided in the container by default at
initialization. On failure to build the dependency graph, it is attached
to the error and if possible, colorized to highlight the root cause of the
failure.
type ErrorHandler ¶
added in
v1.7.0
type ErrorHandler interface {
HandleError(error)
}
ErrorHandler handles Fx application startup errors.
type Hook ¶
type Hook struct {
OnStart func(context.Context) error
OnStop func(context.Context) error
// contains filtered or unexported fields
}
A Hook is a pair of start and stop callbacks, either of which can be nil.
If a Hook's OnStart callback isn't executed (because a previous OnStart
failure short-circuited application startup), its OnStop callback won't be
executed.
func StartHook ¶
added in
v1.19.0
func StartHook[T HookFunc](start T) Hook
StartHook returns a new Hook with start as its [Hook.OnStart] function,
wrapping its signature as needed. For example, given the following function:
func myhook() {
fmt.Println("hook called")
}
then calling:
lifecycle.Append(StartHook(myfunc))
is functionally equivalent to calling:
lifecycle.Append(fx.Hook{
OnStart: func(context.Context) error {
myfunc()
return nil
},
})
The same is true for all functions that satisfy the HookFunc constraint.
Note that any context.Context parameter or error return will be propagated
as expected. If propagation is not intended, users should instead provide a
closure that discards the undesired value(s), or construct a Hook directly.
func StartStopHook ¶
added in
v1.19.0
func StartStopHook[T1 HookFunc, T2 HookFunc](start T1, stop T2) Hook
StartStopHook returns a new Hook with start as its [Hook.OnStart] function
and stop as its [Hook.OnStop] function, independently wrapping the signature
of each as needed.
func StopHook ¶
added in
v1.19.0
func StopHook[T HookFunc](stop T) Hook
StopHook returns a new Hook with stop as its [Hook.OnStop] function,
wrapping its signature as needed. For example, given the following function:
func myhook() {
fmt.Println("hook called")
}
then calling:
lifecycle.Append(StopHook(myfunc))
is functionally equivalent to calling:
lifecycle.Append(fx.Hook{
OnStop: func(context.Context) error {
myfunc()
return nil
},
})
The same is true for all functions that satisfy the HookFunc constraint.
Note that any context.Context parameter or error return will be propagated
as expected. If propagation is not intended, users should instead provide a
closure that discards the undesired value(s), or construct a Hook directly.
type HookFunc ¶
added in
v1.19.0
type HookFunc interface {
~func() | ~func() error | ~func(context.Context) | ~func(context.Context) error
}
A HookFunc is a function that can be used as a Hook.
type In ¶
type In = dig.In
Parameter Structs
Optional Dependencies
Named Values
Value Groups
Soft Value Groups
Unexported fields
In can be embedded in a constructor's parameter struct to take advantage of
advanced dependency injection features.
Modules should take a single parameter struct that embeds an In in order to
provide a forward-compatible API: since adding fields to a struct is
backward-compatible, modules can then add optional dependencies in minor
releases.
Parameter Structs ¶Fx constructors declare their dependencies as function parameters. This can
quickly become unreadable if the constructor has a lot of dependencies.
func NewHandler(users *UserGateway, comments *CommentGateway, posts *PostGateway, votes *VoteGateway, authz *AuthZGateway) *Handler {
// ...
}
To improve the readability of constructors like this, create a struct that
lists all the dependencies as fields and change the function to accept that
struct instead. The new struct is called a parameter struct.
Fx has first class support for parameter structs: any struct embedding
fx.In gets treated as a parameter struct, so the individual fields in the
struct are supplied via dependency injection. Using a parameter struct, we
can make the constructor above much more readable:
type HandlerParams struct {
fx.In
Users *UserGateway
Comments *CommentGateway
Posts *PostGateway
Votes *VoteGateway
AuthZ *AuthZGateway
}
func NewHandler(p HandlerParams) *Handler {
// ...
}
Though it's rarely a good idea, constructors can receive any combination of
parameter structs and parameters.
func NewHandler(p HandlerParams, l *log.Logger) *Handler {
// ...
}
Optional Dependencies ¶Constructors often have optional dependencies on some types: if those types are
missing, they can operate in a degraded state. Fx supports optional
dependencies via the `optional:"true"` tag to fields on parameter structs.
type UserGatewayParams struct {
fx.In
Conn *sql.DB
Cache *redis.Client `optional:"true"`
}
If an optional field isn't available in the container, the constructor
receives the field's zero value.
func NewUserGateway(p UserGatewayParams, log *log.Logger) (*UserGateway, error) {
if p.Cache == nil {
log.Print("Caching disabled")
}
// ...
}
Constructors that declare optional dependencies MUST gracefully handle
situations in which those dependencies are absent.
The optional tag also allows adding new dependencies without breaking
existing consumers of the constructor.
Named Values ¶Some use cases require the application container to hold multiple values of
the same type. For details on producing named values, see the documentation
for the Out type.
Fx allows functions to consume named values via the `name:".."` tag on
parameter structs. Note that both the name AND type of the fields on the
parameter struct must match the corresponding result struct.
type GatewayParams struct {
fx.In
WriteToConn *sql.DB `name:"rw"`
ReadFromConn *sql.DB `name:"ro"`
}
The name tag may be combined with the optional tag to declare the
dependency optional.
type GatewayParams struct {
fx.In
WriteToConn *sql.DB `name:"rw"`
ReadFromConn *sql.DB `name:"ro" optional:"true"`
}
func NewCommentGateway(p GatewayParams, log *log.Logger) (*CommentGateway, error) {
if p.ReadFromConn == nil {
log.Print("Warning: Using RW connection for reads")
p.ReadFromConn = p.WriteToConn
}
// ...
}
Value Groups ¶To make it easier to produce and consume many values of the same type, Fx
supports named, unordered collections called value groups. For details on
producing value groups, see the documentation for the Out type.
Functions can depend on a value group by requesting a slice tagged with
`group:".."`. This will execute all constructors that provide a value to
that group in an unspecified order, then collect all the results into a
single slice. Keep in mind that this makes the types of the parameter and
result struct fields different: if a group of constructors each returns
type T, parameter structs consuming the group must use a field of type []T.
type ServerParams struct {
fx.In
Handlers []Handler `group:"server"`
}
func NewServer(p ServerParams) *Server {
server := newServer()
for _, h := range p.Handlers {
server.Register(h)
}
return server
}
Note that values in a value group are unordered. Fx makes no guarantees
about the order in which these values will be produced.
Soft Value Groups ¶A soft value group can be thought of as a best-attempt at populating the
group with values from constructors that have already run. In other words,
if a constructor's output type is only consumed by a soft value group,
it will not be run.
Note that Fx does not guarantee precise execution order of constructors
or invokers, which means that the change in code that affects execution
ordering of other constructors or functions will affect the values
populated in this group.
To declare a soft relationship between a group and its constructors, use
the `soft` option on the group tag (`group:"[groupname],soft"`).
This option is only valid for input parameters.
type Params struct {
fx.In
Handlers []Handler `group:"server,soft"`
Logger *zap.Logger
}
NewHandlerAndLogger := func() (Handler, *zap.Logger) { ... }
NewHandler := func() Handler { ... }
Foo := func(Params) { ... }
app := fx.New(
fx.Provide(fx.Annotate(NewHandlerAndLogger, fx.ResultTags(`group:"server"`))),
fx.Provide(fx.Annotate(NewHandler, fx.ResultTags(`group::"server"`))),
fx.Invoke(Foo),
)
The only constructor called is `NewHandlerAndLogger`, because this also provides
`*zap.Logger` needed in the `Params` struct received by `Foo`. The Handlers
group will be populated with a single Handler returned by `NewHandlerAndLogger`.
In the next example, the slice `s` isn't populated as the provider would be
called only because `strings` soft group value is its only consumer.
app := fx.New(
fx.Provide(
fx.Annotate(
func() (string, int) { return "hello", 42 },
fx.ResultTags(`group:"strings"`),
),
),
fx.Invoke(
fx.Annotate(func(s []string) {
// s will be an empty slice
}, fx.ParamTags(`group:"strings,soft"`)),
),
)
In the next example, the slice `s` will be populated because there is a
consumer for the same type which is not a `soft` dependency.
app := fx.New(
fx.Provide(
fx.Annotate(
func() string { "hello" },
fx.ResultTags(`group:"strings"`),
),
),
fx.Invoke(
fx.Annotate(func(b []string) {
// b is []string{"hello"}
}, fx.ParamTags(`group:"strings"`)),
),
fx.Invoke(
fx.Annotate(func(s []string) {
// s is []string{"hello"}
}, fx.ParamTags(`group:"strings,soft"`)),
),
)
Unexported fields ¶By default, a type that embeds fx.In may not have any unexported fields. The
following will return an error if used with Fx.
type Params struct {
fx.In
Logger *zap.Logger
mu sync.Mutex
}
If you have need of unexported fields on such a type, you may opt-into
ignoring unexported fields by adding the ignore-unexported struct tag to the
fx.In. For example,
type Params struct {
fx.In `ignore-unexported:"true"`
Logger *zap.Logger
mu sync.Mutex
}
type Lifecycle ¶
type Lifecycle interface {
Append(Hook)
}
Lifecycle allows constructors to register callbacks that are executed on
application start and stop. See the documentation for App for details on Fx
applications' initialization, startup, and shutdown logic.
type Option ¶
type Option interface {
fmt.Stringer
// contains filtered or unexported methods
}
An Option configures an App using the functional options paradigm
popularized by Rob Pike. If you're unfamiliar with this style, see
https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html.
func Decorate ¶
added in
v1.17.0
func Decorate(decorators ...interface{}) Option
Decorator functions
Decorator scope
Decorate specifies one or more decorator functions to an Fx application.
Decorator functions ¶Decorator functions let users augment objects in the graph.
They can take in zero or more dependencies that must be provided to the
application with fx.Provide, and produce one or more values that can be used
by other fx.Provide and fx.Invoke calls.
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("myapp")
})
fx.Invoke(func(log *zap.Logger) {
log.Info("hello")
// Output:
// {"level": "info","logger":"myapp","msg":"hello"}
})
The following decorator accepts multiple dependencies from the graph,
augments and returns one of them.
fx.Decorate(func(log *zap.Logger, cfg *Config) *zap.Logger {
return log.Named(cfg.Name)
})
Similar to fx.Provide, functions passed to fx.Decorate may optionally return
an error as their last result.
If a decorator returns a non-nil error, it will halt application startup.
fx.Decorate(func(conn *sql.DB, cfg *Config) (*sql.DB, error) {
if err := conn.Ping(); err != nil {
return sql.Open("driver-name", cfg.FallbackDB)
}
return conn, nil
})
Decorators support both, fx.In and fx.Out structs, similar to fx.Provide and
fx.Invoke.
type Params struct {
fx.In
Client usersvc.Client `name:"readOnly"`
}
type Result struct {
fx.Out
Client usersvc.Client `name:"readOnly"`
}
fx.Decorate(func(p Params) Result {
...
})
Decorators can be annotated with the fx.Annotate function, but not with the
fx.Annotated type. Refer to documentation on fx.Annotate() to learn how to
use it for annotating functions.
fx.Decorate(
fx.Annotate(
func(client usersvc.Client) usersvc.Client {
// ...
},
fx.ParamTags(`name:"readOnly"`),
fx.ResultTags(`name:"readOnly"`),
),
)
Decorators support augmenting, filtering, or replacing value groups.
To decorate a value group, expect the entire value group slice and produce
the new slice.
type HandlerParam struct {
fx.In
Log *zap.Logger
Handlers []Handler `group:"server"
}
type HandlerResult struct {
fx.Out
Handlers []Handler `group:"server"
}
fx.Decorate(func(p HandlerParam) HandlerResult {
var r HandlerResult
for _, handler := range p.Handlers {
r.Handlers = append(r.Handlers, wrapWithLogger(p.Log, handler))
}
return r
}),
Decorator scope ¶Modifications made to the Fx graph with fx.Decorate are scoped to the
deepest fx.Module inside which the decorator was specified.
fx.Module("mymodule",
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("myapp")
}),
fx.Invoke(func(log *zap.Logger) {
log.Info("decorated logger")
// Output:
// {"level": "info","logger":"myapp","msg":"decorated logger"}
}),
),
fx.Invoke(func(log *zap.Logger) {
log.Info("plain logger")
// Output:
// {"level": "info","msg":"plain logger"}
}),
Decorations specified in the top-level fx.New call apply across the
application and chain with module-specific decorators.
fx.New(
// ...
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.With(zap.Field("service", "myservice"))
}),
// ...
fx.Invoke(func(log *zap.Logger) {
log.Info("outer decorator")
// Output:
// {"level": "info","service":"myservice","msg":"outer decorator"}
}),
// ...
fx.Module("mymodule",
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("myapp")
}),
fx.Invoke(func(log *zap.Logger) {
log.Info("inner decorator")
// Output:
// {"level": "info","logger":"myapp","service":"myservice","msg":"inner decorator"}
}),
),
)
func Error ¶
added in
v1.6.0
func Error(errs ...error) Option
Error registers any number of errors with the application to short-circuit
startup. If more than one error is given, the errors are combined into a
single error.
Similar to invocations, errors are applied in order. All Provide and Invoke
options registered before or after an Error option will not be applied.
Example ¶
package main
import (
"errors"
"fmt"
"net/http"
"os"
"go.uber.org/fx"
)
func main() {
// A module that provides a HTTP server depends on
// the $PORT environment variable. If the variable
// is unset, the module returns an fx.Error option.
newHTTPServer := func() fx.Option {
port := os.Getenv("PORT")
if port == "" {
return fx.Error(errors.New("$PORT is not set"))
}
return fx.Provide(&http.Server{
Addr: fmt.Sprintf("127.0.0.1:%s", port),
})
}
app := fx.New(
fx.NopLogger,
newHTTPServer(),
fx.Invoke(func(s *http.Server) error { return s.ListenAndServe() }),
)
fmt.Println(app.Err())
}
Output:
$PORT is not set
Share
Format
Run
func ErrorHook ¶
added in
v1.7.0
func ErrorHook(funcs ...ErrorHandler) Option
ErrorHook registers error handlers that implement error handling functions.
They are executed on invoke failures. Passing multiple ErrorHandlers appends
the new handlers to the application's existing list.
func Extract
deprecated
func Extract(target interface{}) Option
Extract fills the given struct with values from the dependency injection
container on application initialization. The target MUST be a pointer to a
struct. Only exported fields will be filled.
Deprecated: Use Populate instead.
func Invoke ¶
func Invoke(funcs ...interface{}) Option
Invoke registers functions that are executed eagerly on application start.
Arguments for these invocations are built using the constructors registered
by Provide. Passing multiple Invoke options appends the new invocations to
the application's existing list.
Unlike constructors, invocations are always executed, and they're always
run in order. Invocations may have any number of returned values.
If the final returned object is an error, it indicates whether the operation
was successful.
All other returned values are discarded.
Invokes registered in [Module]s are run before the ones registered at the
scope of the parent. Invokes within the same Module is run in the order
they were provided. For example,
fx.New(
fx.Invoke(func3),
fx.Module("someModule",
fx.Invoke(func1),
fx.Invoke(func2),
),
fx.Invoke(func4),
)
invokes func1, func2, func3, func4 in that order.
Typically, invoked functions take a handful of high-level objects (whose
constructors depend on lower-level objects) and introduce them to each
other. This kick-starts the application by forcing it to instantiate a
variety of types.
To see an invocation in use, read through the package-level example. For
advanced features, including optional parameters and named instances, see
the documentation of the In and Out types.
func Logger ¶
func Logger(p Printer) Option
Logger redirects the application's log output to the provided printer.
Deprecated: use WithLogger instead.
func Module ¶
added in
v1.17.0
func Module(name string, opts ...Option) Option
Module is a named group of zero or more fx.Options.
A Module creates a scope in which certain operations are taken
place. For more information, see Decorate, Replace, or Invoke.
func Options ¶
func Options(opts ...Option) Option
Options converts a collection of Options into a single Option. This allows
packages to bundle sophisticated functionality into easy-to-use Fx modules.
For example, a logging package might export a simple option like this:
package logging
var Module = fx.Provide(func() *log.Logger {
return log.New(os.Stdout, "", 0)
})
A shared all-in-one microservice package could then use Options to bundle
logging with similar metrics, tracing, and gRPC modules:
package server
var Module = fx.Options(
logging.Module,
metrics.Module,
tracing.Module,
grpc.Module,
)
Since this all-in-one module has a minimal API surface, it's easy to add
new functionality to it without breaking existing users. Individual
applications can take advantage of all this functionality with only one
line of code:
app := fx.New(server.Module)
Use this pattern sparingly, since it limits the user's ability to customize
their application.
func Populate ¶
added in
v1.4.0
func Populate(targets ...interface{}) Option
Populate sets targets with values from the dependency injection container
during application initialization. All targets must be pointers to the
values that must be populated. Pointers to structs that embed In are
supported, which can be used to populate multiple values in a struct.
Annotating each pointer with ParamTags is also supported as a shorthand
to passing a pointer to a struct that embeds In with field tags. For example:
var a A
var b B
fx.Populate(
fx.Annotate(
&a,
fx.ParamTags(`name:"A"`)
),
fx.Annotate(
&b,
fx.ParamTags(`name:"B"`)
)
)
Code above is equivalent to the following:
type Target struct {
fx.In
a A `name:"A"`
b B `name:"B"`
}
var target Target
...
fx.Populate(&target)
This is most helpful in unit tests: it lets tests leverage Fx's automatic
constructor wiring to build a few structs, but then extract those structs
for further testing.
Example ¶
package main
import (
"context"
"fmt"
"go.uber.org/fx"
)
func main() {
// Some external module that provides a user name.
type Username string
UserModule := fx.Provide(func() Username { return "john" })
// We want to use Fx to wire up our constructors, but don't actually want to
// run the application - we just want to yank out the user name.
//
// This is common in unit tests, and is even easier with the fxtest
// package's RequireStart and RequireStop helpers.
var user Username
app := fx.New(
UserModule,
fx.NopLogger, // silence test output
fx.Populate(&user),
)
if err := app.Start(context.Background()); err != nil {
panic(err)
}
defer app.Stop(context.Background())
fmt.Println(user)
}
Output:
john
Share
Format
Run
func Provide ¶
func Provide(constructors ...interface{}) Option
Provide registers any number of constructor functions, teaching the
application how to instantiate various types. The supplied constructor
function(s) may depend on other types available in the application, must
return one or more objects, and may return an error. For example:
// Constructs type *C, depends on *A and *B.
func(*A, *B) *C
// Constructs type *C, depends on *A and *B, and indicates failure by
// returning an error.
func(*A, *B) (*C, error)
// Constructs types *B and *C, depends on *A, and can fail.
func(*A) (*B, *C, error)
The order in which constructors are provided doesn't matter, and passing
multiple Provide options appends to the application's collection of
constructors. Constructors are called only if one or more of their returned
types are needed, and their results are cached for reuse (so instances of a
type are effectively singletons within an application). Taken together,
these properties make it perfectly reasonable to Provide a large number of
constructors even if only a fraction of them are used.
See the documentation of the In and Out types for advanced features,
including optional parameters and named instances.
See the documentation for Private for restricting access to constructors.
Constructor functions should perform as little external interaction as
possible, and should avoid spawning goroutines. Things like server listen
loops, background timer loops, and background processing goroutines should
instead be managed using Lifecycle callbacks.
func RecoverFromPanics ¶
added in
v1.19.0
func RecoverFromPanics() Option
RecoverFromPanics causes panics that occur in functions given to Provide,
Decorate, and Invoke to be recovered from.
This error can be retrieved as any other error, by using (*App).Err().
func Replace ¶
added in
v1.17.0
func Replace(values ...interface{}) Option
Replace Caveats
Replace provides instantiated values for graph modification as if
they had been provided using a decorator with fx.Decorate.
The most specific type of each value (as determined by reflection) is used.
Refer to the documentation on fx.Decorate to see how graph modifications
work with fx.Module.
This serves a purpose similar to what fx.Supply does for fx.Provide.
For example, given,
var log *zap.Logger = ...
The following two forms are equivalent.
fx.Replace(log)
fx.Decorate(
func() *zap.Logger {
return log
},
)
Replace panics if a value (or annotation target) is an untyped nil or an error.
Replace Caveats ¶As mentioned above, Replace uses the most specific type of the provided
value. For interface values, this refers to the type of the implementation,
not the interface. So if you try to replace an io.Writer, fx.Replace will
use the type of the implementation.
var stderr io.Writer = os.Stderr
fx.Replace(stderr)
Is equivalent to,
fx.Decorate(func() *os.File { return os.Stderr })
This is typically NOT what you intended. To replace the io.Writer in the
container with the value above, we need to use the fx.Annotate function with
the fx.As annotation.
fx.Replace(
fx.Annotate(os.Stderr, fx.As(new(io.Writer)))
)
func StartTimeout ¶
added in
v1.5.0
func StartTimeout(v time.Duration) Option
StartTimeout changes the application's start timeout.
func StopTimeout ¶
added in
v1.5.0
func StopTimeout(v time.Duration) Option
StopTimeout changes the application's stop timeout.
func Supply ¶
added in
v1.12.0
func Supply(values ...interface{}) Option
Supply Caveats
Supply provides instantiated values for dependency injection as if
they had been provided using a constructor that simply returns them.
The most specific type of each value (as determined by reflection) is used.
This serves a purpose similar to what fx.Replace does for fx.Decorate.
For example, given:
type (
TypeA struct{}
TypeB struct{}
TypeC struct{}
)
var a, b, c = &TypeA{}, TypeB{}, &TypeC{}
The following two forms are equivalent:
fx.Supply(a, b, fx.Annotated{Target: c})
fx.Provide(
func() *TypeA { return a },
func() TypeB { return b },
fx.Annotated{Target: func() *TypeC { return c }},
)
Supply panics if a value (or annotation target) is an untyped nil or an error.
Supply Caveats ¶As mentioned above, Supply uses the most specific type of the provided
value. For interface values, this refers to the type of the implementation,
not the interface. So if you supply an http.Handler, fx.Supply will use the
type of the implementation.
var handler http.Handler = http.HandlerFunc(f)
fx.Supply(handler)
Is equivalent to,
fx.Provide(func() http.HandlerFunc { return f })
This is typically NOT what you intended. To supply the handler above as an
http.Handler, we need to use the fx.Annotate function with the fx.As
annotation.
fx.Supply(
fx.Annotate(handler, fx.As(new(http.Handler))),
)
func WithLogger ¶
added in
v1.14.0
func WithLogger(constructor interface{}) Option
WithLogger specifies how Fx should build an fxevent.Logger to log its events
to. The argument must be a constructor with one of the following return
types.
fxevent.Logger
(fxevent.Logger, error)
For example,
WithLogger(func(logger *zap.Logger) fxevent.Logger {
return &fxevent.ZapLogger{Logger: logger}
})
type Out ¶
type Out = dig.Out
Result Structs
Named Values
Value Groups
Out is the inverse of In: it can be embedded in result structs to take
advantage of advanced features.
Modules should return a single result struct that embeds an Out in order to
provide a forward-compatible API: since adding fields to a struct is
backward-compatible, minor releases can provide additional types.
Result Structs ¶Result structs are the inverse of parameter structs (discussed in the In
documentation). These structs represent multiple outputs from a
single function as fields. Fx treats all structs embedding fx.Out as result
structs, so other constructors can rely on the result struct's fields
directly.
Without result structs, we sometimes have function definitions like this:
func SetupGateways(conn *sql.DB) (*UserGateway, *CommentGateway, *PostGateway, error) {
// ...
}
With result structs, we can make this both more readable and easier to
modify in the future:
type Gateways struct {
fx.Out
Users *UserGateway
Comments *CommentGateway
Posts *PostGateway
}
func SetupGateways(conn *sql.DB) (Gateways, error) {
// ...
}
Named Values ¶Some use cases require the application container to hold multiple values of
the same type. For details on consuming named values, see the documentation
for the In type.
A constructor that produces a result struct can tag any field with
`name:".."` to have the corresponding value added to the graph under the
specified name. An application may contain at most one unnamed value of a
given type, but may contain any number of named values of the same type.
type ConnectionResult struct {
fx.Out
ReadWrite *sql.DB `name:"rw"`
ReadOnly *sql.DB `name:"ro"`
}
func ConnectToDatabase(...) (ConnectionResult, error) {
// ...
return ConnectionResult{ReadWrite: rw, ReadOnly: ro}, nil
}
Value Groups ¶To make it easier to produce and consume many values of the same type, Fx
supports named, unordered collections called value groups. For details on
consuming value groups, see the documentation for the In type.
Constructors can send values into value groups by returning a result struct
tagged with `group:".."`.
type HandlerResult struct {
fx.Out
Handler Handler `group:"server"`
}
func NewHelloHandler() HandlerResult {
// ...
}
func NewEchoHandler() HandlerResult {
// ...
}
Any number of constructors may provide values to this named collection, but
the ordering of the final collection is unspecified. Keep in mind that
value groups require parameter and result structs to use fields with
different types: if a group of constructors each returns type T, parameter
structs consuming the group must use a field of type []T.
To provide multiple values for a group from a result struct, produce a
slice and use the `,flatten` option on the group tag. This indicates that
each element in the slice should be injected into the group individually.
type IntResult struct {
fx.Out
Handler []int `group:"server"` // Consume as [][]int
Handler []int `group:"server,flatten"` // Consume as []int
}
type Printer ¶
type Printer interface {
Printf(string, ...interface{})
}
Printer is the interface required by Fx's logging backend. It's implemented
by most loggers, including the one bundled with the standard library.
Note, this will be deprecate with next release and you will need to implement
fxevent.Logger interface instead.
type ShutdownOption ¶
added in
v1.9.0
type ShutdownOption interface {
// contains filtered or unexported methods
}
ShutdownOption provides a way to configure properties of the shutdown
process. Currently, no options have been implemented.
func ExitCode ¶
added in
v1.19.0
func ExitCode(code int) ShutdownOption
ExitCode is a ShutdownOption that may be passed to the Shutdown method of the
Shutdowner interface.
The given integer exit code will be broadcasted to any receiver waiting
on a ShutdownSignal from the [Wait] method.
func ShutdownTimeout
deprecated
added in
v1.19.0
func ShutdownTimeout(timeout time.Duration) ShutdownOption
ShutdownTimeout is a ShutdownOption that allows users to specify a timeout
for a given call to Shutdown method of the Shutdowner interface. As the
Shutdown method will block while waiting for a signal receiver relay
goroutine to stop.
Deprecated: This option has no effect. Shutdown is not a blocking operation.
type ShutdownSignal ¶
added in
v1.19.0
type ShutdownSignal struct {
Signal os.Signal
ExitCode int
}
ShutdownSignal represents a signal to be written to Wait or Done.
Should a user call the Shutdown method via the Shutdowner interface with
a provided ExitCode, that exit code will be populated in the ExitCode field.
Should the application receive an operating system signal,
the Signal field will be populated with the received os.Signal.
func (ShutdownSignal) String ¶
added in
v1.19.0
func (sig ShutdownSignal) String() string
String will render a ShutdownSignal type as a string suitable for printing.
type Shutdowner ¶
added in
v1.9.0
type Shutdowner interface {
Shutdown(...ShutdownOption) error
}
Shutdowner provides a method that can manually trigger the shutdown of the
application by sending a signal to all open Done channels. Shutdowner works
on applications using Run as well as Start, Done, and Stop. The Shutdowner is
provided to all Fx applications.
Source Files
¶
View all Source files
annotated.go
app.go
app_unixes.go
decorate.go
doc.go
extract.go
inout.go
invoke.go
lifecycle.go
log.go
module.go
populate.go
printer_writer.go
provide.go
replace.go
shutdown.go
signal.go
supply.go
version.go
Directories
¶
Show internal
Expand all
Path
Synopsis
docs
module
fxevent
fxtest
internal
fxclock
fxlog
fxlog/foovendor
fxlog/sample.git
fxreflect
lifecycle
testutil
e2e
Module
tools
module
Click to show internal directories.
Click to hide internal directories.
Why Go
Use Cases
Case Studies
Get Started
Playground
Tour
Stack Overflow
Help
Packages
Standard Library
Sub-repositories
About Go Packages
About
Download
Blog
Issue Tracker
Release Notes
Brand Guidelines
Code of Conduct
Connect
GitHub
Slack
r/golang
Meetup
Golang Weekly
Copyright
Terms of Service
Privacy Policy
Report an Issue
Theme Toggle
Shortcuts Modal
Jump to
Close
Keyboard shortcuts
? : This menu
/ : Search site
f or F : Jump to
y or Y
: Canonical URL
Close
go.dev uses cookies from Google to deliver and enhance the quality of its services and to
analyze traffic. Learn more.
Okay
golang使用Fx实现自动依赖注入| 青训营笔记 - 掘金
golang使用Fx实现自动依赖注入| 青训营笔记 - 掘金
首页 首页
沸点
课程
直播
活动
竞赛
商城
APP
插件 搜索历史
清空
创作者中心
写文章 发沸点 写笔记 写代码 草稿箱 创作灵感
查看更多
会员
登录
注册
golang使用Fx实现自动依赖注入| 青训营笔记
悠影
2022-06-12
754
这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记。
关于依赖注入是什么不想废话,java里面spring就是大量使用了依赖注入,想要理解依赖注入的思想直接看其他的文章就行,这里直接抛干货。
例子
首先我们在logger.New()里面构造一个zap.Logger对象。
var (
logger *zap.Logger
once sync.Once
)
// 创建新的logger
func New(cfg *config.Config) *zap.Logger {
once.Do(func() {
logger, _ = zap.NewProduction()
})
return logger
}
我们还需要一个S3ObjectAPI的Interface,他提供PutObject和PresignGetObject的方法。我们有一个s3.New()的构造函数,需要我们提供config和logger两个对象进行构造,返回一个Mys3对象并有对应S3ObjectAPI的方法。
type Mys3 struct {
s3 *s3.Client
}
var (
s3Client *Mys3
once sync.Once
)
func New(config *config.Config, logger *zap.Logger) S3ObjectAPI {
if !config.S3.Vaild {
return nil
}
once.Do(func() {
// your create s3 client code here...
var client s3.client
s3Client = &Mys3{
s3: &client,
}
})
return s3Client
}
type S3ObjectAPI interface {
PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
PresignGetObject(ctx context.Context, input *s3.GetObjectInput, optFns ...func(*s3.PresignOptions)) (*v4.PresignedHTTPRequest, error)
}
我们的Service层也有一个service.New()的构造函数,他依赖我们上面构造的Logger对象和S3ObjectAPI接口。
type Service struct {
cfg *config.Config
db *gorm.DB
rds *redis.Client
logger *zap.Logger
producer sarama.AsyncProducer
s3 s3Object.S3ObjectAPI
}
var (
service *Service
once sync.Once
)
// 启动一个新的service实例,当然是单例模式
func New(cfg *config.Config, db *gorm.DB,
rds *redis.Client,
logger *zap.Logger,
producer sarama.AsyncProducer,
s3 s3Object.S3ObjectAPI) *Service {
once.Do(func() {
service = &Service{
cfg: cfg,
db: db,
rds: rds,
logger: logger,
producer: producer,
s3: s3,
}
})
return service
}
这样的对象需要在main.go里面进行手动注入。你需要自己手动处理好他们的依赖关系和构建对象的顺序。
// 这不用依赖注入框架真的好吗。。。
func main() {
cfg, err := config.Phase()
if err != nil {
panic(err)
}
mylogger := logger.New(cfg)
db := mysql.New(cfg, mylogger)
rds := redis.New(cfg, mylogger)
pdc := kafka.NewProducer(cfg)
s3 := s3.New(cfg, mylogger)
ser := service.New(cfg, db, rds, mylogger, pdc, s3)
ctl := controller.New(ser, mylogger)
httpserver.Run(cfg, ctl, mylogger)
}
对于青训营的demo版本的抖音项目来说,这个依赖注入的数量已经比较复杂了。进入真正的商业化项目的开发后,依赖的数量只会比这个更多,届时管理各个对象之间的依赖关系会极大的加重开发者的心理负担。我们的确需要一个工具来帮我们完成依赖注入的任务。
什么是Fx?
Fx是一个Golang下的依赖注入框架,他降低了在Golang中使用依赖注入的难度。
Fx可以和上面的构造函数一起使用而无需嵌入特殊的类型和使用特殊的struct,所以Fx可以轻松的使用在大多数的Go packages中。实际上在青训营项目中,我们也是在后期才引入Fx的。Fx帮我们管理好了对象间的关系,避免了我们在main.go写出臃肿的注入代码。
使用了Fx以后的代码
// 用了 好用
func main() {
app := fx.New(
fx.Provide(
config.Phase,
logger.New,
mysql.New,
redis.New,
kafka.NewProducer,
s3.New,
service.New,
controller.New,
),
fx.Invoke(
httpserver.Run,
),
fx.WithLogger(
func(logger *zap.Logger) fxevent.Logger {
return &fxevent.ZapLogger{Logger: logger}
},
),
)
app.Run()
}
我们把上述的对象的构造函数全部喂给fx.Provice(),然后把最终的http实例直接扔到fx.Invoke()里面,获得一个fx.app,执行这个app的Run方法,应用就跑起来了,实际的效果和上述的main.go的效果是一样的。
参考资料
uber-go/fx: A dependency injection based application framework for Go. (github.com)
fx package - go.uber.org/fx - Go Packages
悠影
学生
5
文章
1.7k
阅读
5
粉丝 目录 收起
例子
什么是Fx?
使用了Fx以后的代码
参考资料
相关推荐 如果失业了,我们还能干啥? 35k阅读 · 419点赞语雀停机事件后,你也在找替代方案吗? 16k阅读 · 74点赞博客搬家 | 2023年的最后一个月,宜在掘金开启写作之旅! 2.9k阅读 · 4点赞程序员自由创业周记#20:需求从何而来 1.9k阅读 · 4点赞28.7k Star! 突破边界、强大易用建站利器 -- Halo 14k阅读 · 152点赞
Introduction | Fx
Introduction | Fx
Fx
Guide
API Reference
(opens new window)
GitHub
(opens new window)
Guide
API Reference
(opens new window)
GitHub
(opens new window) Get Started Create a minimal applicationAdd an HTTP serverRegister a handlerAdd a loggerDecouple registrationRegister another handlerRegister many handlersConclusionIntroductionConcepts Features FAQCommunity Release notes # Introduction Fx is a dependency injection system for Go.
With Fx you can: reduce boilerplate in setting up your application eliminate global state in your application add new components and have them instantly accessible across the application build general purpose shareable modules that just work If this is your first time with Fx,
check out our getting started tutorial. Edit this page (opens new window) Last Updated: 2/20/2024, 4:15:55 PM
←
Conclusion
Container
→
fx package - github.com/uber-go/fx - Go Packages
fx package - github.com/uber-go/fx - Go Packages
Skip to Main Content
Why Go
Case Studies
Common problems companies solve with Go
Use Cases
Stories about how and why companies use Go
Security Policy
How Go can help keep you secure by default
Learn
Docs
Effective Go
Tips for writing clear, performant, and idiomatic Go code
Go User Manual
A complete introduction to building software with Go
Standard library
Reference documentation for Go's standard library
Release Notes
Learn what's new in each Go release
Packages
Community
Recorded Talks
Videos from prior events
Meetups
Meet other local Go developers
Conferences
Learn and network with Go developers from around the world
Go blog
The Go project's official blog.
Go project
Get help and stay informed from Go
Get connected
Why Go
Why Go
Case Studies
Use Cases
Security Policy
Learn
Docs
Docs
Effective Go
Go User Manual
Standard library
Release Notes
Packages
Community
Community
Recorded Talks
Meetups
Conferences
Go blog
Go project
Get connected
Discover Packages
github.com/uber-go/fx
fx
package
module
Version:
v1.9.0
Opens a new window with list of versions in this module.
Latest
Latest
This package is not in the latest version of its module.
Go to latest
Published: Jan 22, 2019
License: MIT
Opens a new window with license information.
Imports: 18
Opens a new window with list of imports.
Imported by: 0
Opens a new window with list of known importers.
Main
Versions
Licenses
Imports
Imported By
Details
Valid go.mod file
The Go module system was introduced in Go 1.11 and is the official dependency management
solution for Go.
Redistributable license
Redistributable licenses place minimal restrictions on how software can be used,
modified, and redistributed.
Tagged version
Modules with tagged versions give importers more predictable builds.
Stable version
When a project reaches major version v1 it is considered stable.
Learn more about best practices
Repository
github.com/uber-go/fx
Links
Open Source Insights
Jump to ...
README
Installation
Stability
Documentation
Overview
Index
Examples
Constants
Variables
Functions
VisualizeError(err)
Types
type Annotated
type App
New(opts)
(app) Done()
(app) Err()
(app) Run()
(app) Start(ctx)
(app) StartTimeout()
(app) Stop(ctx)
(app) StopTimeout()
type DotGraph
type ErrorHandler
type Hook
type In
type Lifecycle
type Option
Error(errs)
ErrorHook(funcs)
Extract(target)
Invoke(funcs)
Logger(p)
Options(opts)
Populate(targets)
Provide(constructors)
StartTimeout(v)
StopTimeout(v)
type Out
type Printer
type ShutdownOption
type Shutdowner
Source Files
Directories
README
README
¶
Fx
An application framework for Go that:
Makes dependency injection easy.
Eliminates the need for global state and func init().
Installation
We recommend locking to SemVer range ^1 using Glide:
glide get 'go.uber.org/fx#^1'
Stability
This library is v1 and follows SemVer strictly.
No breaking changes will be made to exported APIs before v2.0.0.
This project follows the Go Release Policy. Each major
version of Go is supported until there are two newer major releases.
Expand ▾
Collapse ▴
Documentation
¶
Overview ¶
Testing Fx Applications
Package fx is a framework that makes it easy to build applications out of
reusable, composable modules.
Fx applications use dependency injection to eliminate globals without the
tedium of manually wiring together function calls. Unlike other approaches
to dependency injection, Fx works with plain Go functions: you don't need
to use struct tags or embed special types, so Fx automatically works well
with most Go packages.
Basic usage is explained in the package-level example below. If you're new
to Fx, start there! Advanced features, including named instances, optional
parameters, and value groups, are explained under the In and Out types.
Testing Fx Applications ¶To test functions that use the Lifecycle type or to write end-to-end tests
of your Fx application, use the helper functions and types provided by the
go.uber.org/fx/fxtest package.
Example ¶
package main
import (
"context"
"log"
"net/http"
"os"
"time"
"go.uber.org/fx"
)
// NewLogger constructs a logger. It's just a regular Go function, without any
// special relationship to Fx.
//
// Since it returns a *log.Logger, Fx will treat NewLogger as the constructor
// function for the standard library's logger. (We'll see how to integrate
// NewLogger into an Fx application in the main function.) Since NewLogger
// doesn't have any parameters, Fx will infer that loggers don't depend on any
// other types - we can create them from thin air.
//
// Fx calls constructors lazily, so NewLogger will only be called only if some
// other function needs a logger. Once instantiated, the logger is cached and
// reused - within the application, it's effectively a singleton.
//
// By default, Fx applications only allow one constructor for each type. See
// the documentation of the In and Out types for ways around this restriction.
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
// NewHandler constructs a simple HTTP handler. Since it returns an
// http.Handler, Fx will treat NewHandler as the constructor for the
// http.Handler type.
//
// Like many Go functions, NewHandler also returns an error. If the error is
// non-nil, Go convention tells the caller to assume that NewHandler failed
// and the other returned values aren't safe to use. Fx understands this
// idiom, and assumes that any function whose last return value is an error
// follows this convention.
//
// Unlike NewLogger, NewHandler has formal parameters. Fx will interpret these
// parameters as dependencies: in order to construct an HTTP handler,
// NewHandler needs a logger. If the application has access to a *log.Logger
// constructor (like NewLogger above), it will use that constructor or its
// cached output and supply a logger to NewHandler. If the application doesn't
// know how to construct a logger and needs an HTTP handler, it will fail to
// start.
//
// Functions may also return multiple objects. For example, we could combine
// NewHandler and NewLogger into a single function:
//
// func NewHandlerAndLogger() (*log.Logger, http.Handler, error)
//
// Fx also understands this idiom, and would treat NewHandlerAndLogger as the
// constructor for both the *log.Logger and http.Handler types. Just like
// constructors for a single type, NewHandlerAndLogger would be called at most
// once, and both the handler and the logger would be cached and reused as
// necessary.
func NewHandler(logger *log.Logger) (http.Handler, error) {
logger.Print("Executing NewHandler.")
return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
logger.Print("Got a request.")
}), nil
}
// NewMux constructs an HTTP mux. Like NewHandler, it depends on *log.Logger.
// However, it also depends on the Fx-specific Lifecycle interface.
//
// A Lifecycle is available in every Fx application. It lets objects hook into
// the application's start and stop phases. In a non-Fx application, the main
// function often includes blocks like this:
//
// srv, err := NewServer() // some long-running network server
// if err != nil {
// log.Fatalf("failed to construct server: %v", err)
// }
// // Construct other objects as necessary.
// go srv.Start()
// defer srv.Stop()
//
// In this example, the programmer explicitly constructs a bunch of objects,
// crashing the program if any of the constructors encounter unrecoverable
// errors. Once all the objects are constructed, we start any background
// goroutines and defer cleanup functions.
//
// Fx removes the manual object construction with dependency injection. It
// replaces the inline goroutine spawning and deferred cleanups with the
// Lifecycle type.
//
// Here, NewMux makes an HTTP mux available to other functions. Since
// constructors are called lazily, we know that NewMux won't be called unless
// some other function wants to register a handler. This makes it easy to use
// Fx's Lifecycle to start an HTTP server only if we have handlers registered.
func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
logger.Print("Executing NewMux.")
// First, we construct the mux and server. We don't want to start the server
// until all handlers are registered.
mux := http.NewServeMux()
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// If NewMux is called, we know that another function is using the mux. In
// that case, we'll use the Lifecycle type to register a Hook that starts
// and stops our HTTP server.
//
// Hooks are executed in dependency order. At startup, NewLogger's hooks run
// before NewMux's. On shutdown, the order is reversed.
//
// Returning an error from OnStart hooks interrupts application startup. Fx
// immediately runs the OnStop portions of any successfully-executed OnStart
// hooks (so that types which started cleanly can also shut down cleanly),
// then exits.
//
// Returning an error from OnStop hooks logs a warning, but Fx continues to
// run the remaining hooks.
lc.Append(fx.Hook{
// To mitigate the impact of deadlocks in application startup and
// shutdown, Fx imposes a time limit on OnStart and OnStop hooks. By
// default, hooks have a total of 30 seconds to complete. Timeouts are
// passed via Go's usual context.Context.
OnStart: func(context.Context) error {
logger.Print("Starting HTTP server.")
// In production, we'd want to separate the Listen and Serve phases for
// better error-handling.
go server.ListenAndServe()
return nil
},
OnStop: func(ctx context.Context) error {
logger.Print("Stopping HTTP server.")
return server.Shutdown(ctx)
},
})
return mux
}
// Register mounts our HTTP handler on the mux.
//
// Register is a typical top-level application function: it takes a generic
// type like ServeMux, which typically comes from a third-party library, and
// introduces it to a type that contains our application logic. In this case,
// that introduction consists of registering an HTTP handler. Other typical
// examples include registering RPC procedures and starting queue consumers.
//
// Fx calls these functions invocations, and they're treated differently from
// the constructor functions above. Their arguments are still supplied via
// dependency injection and they may still return an error to indicate
// failure, but any other return values are ignored.
//
// Unlike constructors, invocations are called eagerly. See the main function
// below for details.
func Register(mux *http.ServeMux, h http.Handler) {
mux.Handle("/", h)
}
func main() {
app := fx.New(
// Provide all the constructors we need, which teaches Fx how we'd like to
// construct the *log.Logger, http.Handler, and *http.ServeMux types.
// Remember that constructors are called lazily, so this block doesn't do
// much on its own.
fx.Provide(
NewLogger,
NewHandler,
NewMux,
),
// Since constructors are called lazily, we need some invocations to
// kick-start our application. In this case, we'll use Register. Since it
// depends on an http.Handler and *http.ServeMux, calling it requires Fx
// to build those types using the constructors above. Since we call
// NewMux, we also register Lifecycle hooks to start and stop an HTTP
// server.
fx.Invoke(Register),
)
// In a typical application, we could just use app.Run() here. Since we
// don't want this example to run forever, we'll use the more-explicit Start
// and Stop.
startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil {
log.Fatal(err)
}
// Normally, we'd block here with <-app.Done(). Instead, we'll make an HTTP
// request to demonstrate that our server is running.
http.Get("http://localhost:8080/")
stopCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Stop(stopCtx); err != nil {
log.Fatal(err)
}
}
Output:
Executing NewLogger.
Executing NewMux.
Executing NewHandler.
Starting HTTP server.
Got a request.
Stopping HTTP server.
Share
Format
Run
Index ¶
Constants
Variables
func VisualizeError(err error) (string, error)
type Annotated
type App
func New(opts ...Option) *App
func (app *App) Done() <-chan os.Signal
func (app *App) Err() error
func (app *App) Run()
func (app *App) Start(ctx context.Context) error
func (app *App) StartTimeout() time.Duration
func (app *App) Stop(ctx context.Context) error
func (app *App) StopTimeout() time.Duration
type DotGraph
type ErrorHandler
type Hook
type In
type Lifecycle
type Option
func Error(errs ...error) Option
func ErrorHook(funcs ...ErrorHandler) Option
func Extract(target interface{}) Option
func Invoke(funcs ...interface{}) Option
func Logger(p Printer) Option
func Options(opts ...Option) Option
func Populate(targets ...interface{}) Option
func Provide(constructors ...interface{}) Option
func StartTimeout(v time.Duration) Option
func StopTimeout(v time.Duration) Option
type Out
type Printer
type ShutdownOption
type Shutdowner
Examples ¶
Package
Error
Populate
Constants ¶
View Source
const DefaultTimeout = 15 * time.Second
DefaultTimeout is the default timeout for starting or stopping an
application. It can be configured with the StartTimeout and StopTimeout
options.
View Source
const Version = "1.9.0"
Version is exported for runtime compatibility checks.
Variables ¶
View Source
var NopLogger = Logger(nopLogger{})
NopLogger disables the application's log output. Note that this makes some
failures difficult to debug, since no errors are printed to console.
Functions ¶
func VisualizeError ¶
added in
v1.7.0
func VisualizeError(err error) (string, error)
VisualizeError returns the visualization of the error if available.
Types ¶
type Annotated ¶
added in
v1.9.0
type Annotated struct {
// If specified, this will be used as the name for all non-error values returned
// by the constructor. For more information on named values, see the documentation
// for the fx.Out type.
//
// A name option may not be provided if a group option is provided.
Name string
// If specified, this will be used as the group name for all non-error values returned
// by the constructor. For more information on value groups, see the package documentation.
//
// A group option may not be provided if a name option is provided.
Group string
// Target is the constructor being annotated with fx.Annotated.
Target interface{}
}
Annotated annotates a constructor provided to Fx with additional options.
For example,
func NewReadOnlyConnection(...) (*Connection, error)
fx.Provide(fx.Annotated{
Name: "ro",
Target: NewReadOnlyConnection,
})
Is equivalent to,
type result struct {
fx.Out
Connection *Connection `name:"ro"`
}
fx.Provide(func(...) (Result, error) {
conn, err := NewReadOnlyConnection(...)
return Result{Connection: conn}, err
})
Annotated cannot be used with constructors which produce fx.Out objects.
type App ¶
type App struct {
// contains filtered or unexported fields
}
An App is a modular application built around dependency injection. Most
users will only need to use the New constructor and the all-in-one Run
convenience method. In more unusual cases, users may need to use the Err,
Start, Done, and Stop methods by hand instead of relying on Run.
New creates and initializes an App. All applications begin with a
constructor for the Lifecycle type already registered.
In addition to that built-in functionality, users typically pass a handful
of Provide options and one or more Invoke options. The Provide options
teach the application how to instantiate a variety of types, and the Invoke
options describe how to initialize the application.
When created, the application immediately executes all the functions passed
via Invoke options. To supply these functions with the parameters they
need, the application looks for constructors that return the appropriate
types; if constructors for any required types are missing or any
invocations return an error, the application will fail to start (and Err
will return a descriptive error message).
Once all the invocations (and any required constructors) have been called,
New returns and the application is ready to be started using Run or Start.
On startup, it executes any OnStart hooks registered with its Lifecycle.
OnStart hooks are executed one at a time, in order, and must all complete
within a configurable deadline (by default, 15 seconds). For details on the
order in which OnStart hooks are executed, see the documentation for the
Start method.
At this point, the application has successfully started up. If started via
Run, it will continue operating until it receives a shutdown signal from
Done (see the Done documentation for details); if started explicitly via
Start, it will operate until the user calls Stop. On shutdown, OnStop hooks
execute one at a time, in reverse order, and must all complete within a
configurable deadline (again, 15 seconds by default).
func New ¶
func New(opts ...Option) *App
New creates and initializes an App, immediately executing any functions
registered via Invoke options. See the documentation of the App struct for
details on the application's initialization, startup, and shutdown logic.
func (*App) Done ¶
func (app *App) Done() <-chan os.Signal
Done returns a channel of signals to block on after starting the
application. Applications listen for the SIGINT and SIGTERM signals; during
development, users can send the application SIGTERM by pressing Ctrl-C in
the same terminal as the running process.
Alternatively, a signal can be broadcast to all done channels manually by
using the Shutdown functionality (see the Shutdowner documentation for details).
func (*App) Err ¶
func (app *App) Err() error
Err returns any error encountered during New's initialization. See the
documentation of the New method for details, but typical errors include
missing constructors, circular dependencies, constructor errors, and
invocation errors.
Most users won't need to use this method, since both Run and Start
short-circuit if initialization failed.
func (*App) Run ¶
func (app *App) Run()
Run starts the application, blocks on the signals channel, and then
gracefully shuts the application down. It uses DefaultTimeout to set a
deadline for application startup and shutdown, unless the user has
configured different timeouts with the StartTimeout or StopTimeout options.
It's designed to make typical applications simple to run.
However, all of Run's functionality is implemented in terms of the exported
Start, Done, and Stop methods. Applications with more specialized needs
can use those methods directly instead of relying on Run.
func (*App) Start ¶
func (app *App) Start(ctx context.Context) error
Start kicks off all long-running goroutines, like network servers or
message queue consumers. It does this by interacting with the application's
Lifecycle.
By taking a dependency on the Lifecycle type, some of the user-supplied
functions called during initialization may have registered start and stop
hooks. Because initialization calls constructors serially and in dependency
order, hooks are naturally registered in dependency order too.
Start executes all OnStart hooks registered with the application's
Lifecycle, one at a time and in order. This ensures that each constructor's
start hooks aren't executed until all its dependencies' start hooks
complete. If any of the start hooks return an error, Start short-circuits,
calls Stop, and returns the inciting error.
Note that Start short-circuits immediately if the New constructor
encountered any errors in application initialization.
func (*App) StartTimeout ¶
added in
v1.5.0
func (app *App) StartTimeout() time.Duration
StartTimeout returns the configured startup timeout. Apps default to using
DefaultTimeout, but users can configure this behavior using the
StartTimeout option.
func (*App) Stop ¶
func (app *App) Stop(ctx context.Context) error
Stop gracefully stops the application. It executes any registered OnStop
hooks in reverse order, so that each constructor's stop hooks are called
before its dependencies' stop hooks.
If the application didn't start cleanly, only hooks whose OnStart phase was
called are executed. However, all those hooks are executed, even if some
fail.
func (*App) StopTimeout ¶
added in
v1.5.0
func (app *App) StopTimeout() time.Duration
StopTimeout returns the configured shutdown timeout. Apps default to using
DefaultTimeout, but users can configure this behavior using the StopTimeout
option.
type DotGraph ¶
added in
v1.7.0
type DotGraph string
DotGraph contains a DOT language visualization of the dependency graph in
an Fx application. It is provided in the container by default at
initialization. On failure to build the dependency graph, it is attached
to the error and if possible, colorized to highlight the root cause of the
failure.
type ErrorHandler ¶
added in
v1.7.0
type ErrorHandler interface {
HandleError(error)
}
ErrorHandler handles Fx application startup errors.
type Hook ¶
type Hook struct {
OnStart func(context.Context) error
OnStop func(context.Context) error
}
A Hook is a pair of start and stop callbacks, either of which can be nil.
If a Hook's OnStart callback isn't executed (because a previous OnStart
failure short-circuited application startup), its OnStop callback won't be
executed.
type In ¶
type In struct{ dig.In }
Parameter Structs
Optional Dependencies
Named Values
Value Groups
In can be embedded in a constructor's parameter struct to take advantage of
advanced dependency injection features.
Modules should take a single parameter struct that embeds an In in order to
provide a forward-compatible API: since adding fields to a struct is
backward-compatible, modules can then add optional dependencies in minor
releases.
Parameter Structs ¶Fx constructors declare their dependencies as function parameters. This can
quickly become unreadable if the constructor has a lot of dependencies.
func NewHandler(users *UserGateway, comments *CommentGateway, posts *PostGateway, votes *VoteGateway, authz *AuthZGateway) *Handler {
// ...
}
To improve the readability of constructors like this, create a struct that
lists all the dependencies as fields and change the function to accept that
struct instead. The new struct is called a parameter struct.
Fx has first class support for parameter structs: any struct embedding
fx.In gets treated as a parameter struct, so the individual fields in the
struct are supplied via dependency injection. Using a parameter struct, we
can make the constructor above much more readable:
type HandlerParams struct {
fx.In
Users *UserGateway
Comments *CommentGateway
Posts *PostGateway
Votes *VoteGateway
AuthZ *AuthZGateway
}
func NewHandler(p HandlerParams) *Handler {
// ...
}
Though it's rarely a good idea, constructors can receive any combination of
parameter structs and parameters.
func NewHandler(p HandlerParams, l *log.Logger) *Handler {
// ...
}
Optional Dependencies ¶Constructors often have soft dependencies on some types: if those types are
missing, they can operate in a degraded state. Fx supports optional
dependencies via the `optional:"true"` tag to fields on parameter structs.
type UserGatewayParams struct {
fx.In
Conn *sql.DB
Cache *redis.Client `optional:"true"`
}
If an optional field isn't available in the container, the constructor
receives the field's zero value.
func NewUserGateway(p UserGatewayParams, log *log.Logger) (*UserGateway, error) {
if p.Cache != nil {
log.Print("Caching disabled")
}
// ...
}
Constructors that declare optional dependencies MUST gracefully handle
situations in which those dependencies are absent.
The optional tag also allows adding new dependencies without breaking
existing consumers of the constructor.
Named Values ¶Some use cases require the application container to hold multiple values of
the same type. For details on producing named values, see the documentation
for the Out type.
Fx allows functions to consume named values via the `name:".."` tag on
parameter structs. Note that both the name AND type of the fields on the
parameter struct must match the corresponding result struct.
type GatewayParams struct {
fx.In
WriteToConn *sql.DB `name:"rw"`
ReadFromConn *sql.DB `name:"ro"`
}
The name tag may be combined with the optional tag to declare the
dependency optional.
type GatewayParams struct {
fx.In
WriteToConn *sql.DB `name:"rw"`
ReadFromConn *sql.DB `name:"ro" optional:"true"`
}
func NewCommentGateway(p GatewayParams, log *log.Logger) (*CommentGateway, error) {
if p.ReadFromConn == nil {
log.Print("Warning: Using RW connection for reads")
p.ReadFromConn = p.WriteToConn
}
// ...
}
Value Groups ¶To make it easier to produce and consume many values of the same type, Fx
supports named, unordered collections called value groups. For details on
producing value groups, see the documentation for the Out type.
Functions can depend on a value group by requesting a slice tagged with
`group:".."`. This will execute all constructors that provide a value to
that group in an unspecified order, then collect all the results into a
single slice. Keep in mind that this makes the types of the parameter and
result struct fields different: if a group of constructors each returns
type T, parameter structs consuming the group must use a field of type []T.
type ServerParams struct {
fx.In
Handlers []Handler `group:"server"`
}
func NewServer(p ServerParams) *Server {
server := newServer()
for _, h := range p.Handlers {
server.Register(h)
}
return server
}
Note that values in a value group are unordered. Fx makes no guarantees
about the order in which these values will be produced.
type Lifecycle ¶
type Lifecycle interface {
Append(Hook)
}
Lifecycle allows constructors to register callbacks that are executed on
application start and stop. See the documentation for App for details on Fx
applications' initialization, startup, and shutdown logic.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
An Option configures an App using the functional options paradigm
popularized by Rob Pike. If you're unfamiliar with this style, see
https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html.
func Error ¶
added in
v1.6.0
func Error(errs ...error) Option
Error registers any number of errors with the application to short-circuit
startup. If more than one error is given, the errors are combined into a
single error.
Similar to invocations, errors are applied in order. All Provide and Invoke
options registered before or after an Error option will not be applied.
Example ¶
package main
import (
"errors"
"fmt"
"net/http"
"os"
"go.uber.org/fx"
)
func main() {
// A module that provides a HTTP server depends on
// the $PORT environment variable. If the variable
// is unset, the module returns an fx.Error option.
newHTTPServer := func() fx.Option {
port := os.Getenv("PORT")
if port == "" {
return fx.Error(errors.New("$PORT is not set"))
}
return fx.Provide(&http.Server{
Addr: fmt.Sprintf(":%s", port),
})
}
app := fx.New(
newHTTPServer(),
fx.Invoke(func(s *http.Server) error { return s.ListenAndServe() }),
)
fmt.Println(app.Err())
}
Output:
$PORT is not set
Share
Format
Run
func ErrorHook ¶
added in
v1.7.0
func ErrorHook(funcs ...ErrorHandler) Option
ErrorHook registers error handlers that implement error handling functions.
They are executed on invoke failures. Passing multiple ErrorHandlers appends
the new handlers to the application's existing list.
func Extract ¶
func Extract(target interface{}) Option
Extract fills the given struct with values from the dependency injection
container on application initialization. The target MUST be a pointer to a
struct. Only exported fields will be filled.
Extract will be deprecated soon: use Populate instead, which doesn't
require defining a container struct.
func Invoke ¶
func Invoke(funcs ...interface{}) Option
Invoke registers functions that are executed eagerly on application start.
Arguments for these invocations are built using the constructors registered
by Provide. Passing multiple Invoke options appends the new invocations to
the application's existing list.
Unlike constructors, invocations are always executed, and they're always
run in order. Invocations may have any number of returned values. If the
final returned object is an error, it's assumed to be a success indicator.
All other returned values are discarded.
Typically, invoked functions take a handful of high-level objects (whose
constructors depend on lower-level objects) and introduce them to each
other. This kick-starts the application by forcing it to instantiate a
variety of types.
To see an invocation in use, read through the package-level example. For
advanced features, including optional parameters and named instances, see
the documentation of the In and Out types.
func Logger ¶
func Logger(p Printer) Option
Logger redirects the application's log output to the provided printer.
func Options ¶
func Options(opts ...Option) Option
Options converts a collection of Options into a single Option. This allows
packages to bundle sophisticated functionality into easy-to-use Fx modules.
For example, a logging package might export a simple option like this:
package logging
var Module = fx.Provide(func() *log.Logger {
return log.New(os.Stdout, "", 0)
})
A shared all-in-one microservice package could then use Options to bundle
logging with similar metrics, tracing, and gRPC modules:
package server
var Module = fx.Options(
logging.Module,
metrics.Module,
tracing.Module,
grpc.Module,
)
Since this all-in-one module has a minimal API surface, it's easy to add
new functionality to it without breaking existing users. Individual
applications can take advantage of all this functionality with only one
line of code:
app := fx.New(server.Module)
Use this pattern sparingly, since it limits the user's ability to customize
their application.
func Populate ¶
added in
v1.4.0
func Populate(targets ...interface{}) Option
Populate sets targets with values from the dependency injection container
during application initialization. All targets must be pointers to the
values that must be populated. Pointers to structs that embed In are
supported, which can be used to populate multiple values in a struct.
This is most helpful in unit tests: it lets tests leverage Fx's automatic
constructor wiring to build a few structs, but then extract those structs
for further testing.
Example ¶
package main
import (
"context"
"fmt"
"go.uber.org/fx"
)
func main() {
// Some external module that provides a user name.
type Username string
UserModule := fx.Provide(func() Username { return "john" })
// We want to use Fx to wire up our constructors, but don't actually want to
// run the application - we just want to yank out the user name.
//
// This is common in unit tests, and is even easier with the fxtest
// package's RequireStart and RequireStop helpers.
var user Username
app := fx.New(
UserModule,
fx.Populate(&user),
)
if err := app.Start(context.Background()); err != nil {
panic(err)
}
defer app.Stop(context.Background())
fmt.Println(user)
}
Output:
john
Share
Format
Run
func Provide ¶
func Provide(constructors ...interface{}) Option
Provide registers any number of constructor functions, teaching the
application how to instantiate various types. The supplied constructor
function(s) may depend on other types available in the application, must
return one or more objects, and may return an error. For example:
// Constructs type *C, depends on *A and *B.
func(*A, *B) *C
// Constructs type *C, depends on *A and *B, and indicates failure by
// returning an error.
func(*A, *B) (*C, error)
// Constructs types *B and *C, depends on *A, and can fail.
func(*A) (*B, *C, error)
The order in which constructors are provided doesn't matter, and passing
multiple Provide options appends to the application's collection of
constructors. Constructors are called only if one or more of their returned
types are needed, and their results are cached for reuse (so instances of a
type are effectively singletons within an application). Taken together,
these properties make it perfectly reasonable to Provide a large number of
constructors even if only a fraction of them are used.
See the documentation of the In and Out types for advanced features,
including optional parameters and named instances.
func StartTimeout ¶
added in
v1.5.0
func StartTimeout(v time.Duration) Option
StartTimeout changes the application's start timeout.
func StopTimeout ¶
added in
v1.5.0
func StopTimeout(v time.Duration) Option
StopTimeout changes the application's stop timeout.
type Out ¶
type Out struct{ dig.Out }
Result Structs
Named Values
Value Groups
Out is the inverse of In: it can be embedded in result structs to take
advantage of advanced features.
Modules should return a single result struct that embeds an Out in order to
provide a forward-compatible API: since adding fields to a struct is
backward-compatible, minor releases can provide additional types.
Result Structs ¶Result structs are the inverse of parameter structs (discussed in the In
documentation). These structs represent multiple outputs from a
single function as fields. Fx treats all structs embedding fx.Out as result
structs, so other constructors can rely on the result struct's fields
directly.
Without result structs, we sometimes have function definitions like this:
func SetupGateways(conn *sql.DB) (*UserGateway, *CommentGateway, *PostGateway, error) {
// ...
}
With result structs, we can make this both more readable and easier to
modify in the future:
type Gateways struct {
fx.Out
Users *UserGateway
Comments *CommentGateway
Posts *PostGateway
}
func SetupGateways(conn *sql.DB) (Gateways, error) {
// ...
}
Named Values ¶Some use cases require the application container to hold multiple values of
the same type. For details on consuming named values, see the documentation
for the In type.
A constructor that produces a result struct can tag any field with
`name:".."` to have the corresponding value added to the graph under the
specified name. An application may contain at most one unnamed value of a
given type, but may contain any number of named values of the same type.
type ConnectionResult struct {
fx.Out
ReadWrite *sql.DB `name:"rw"`
ReadOnly *sql.DB `name:"ro"`
}
func ConnectToDatabase(...) (ConnectionResult, error) {
// ...
return ConnectionResult{ReadWrite: rw, ReadOnly: ro}, nil
}
Value Groups ¶To make it easier to produce and consume many values of the same type, Fx
supports named, unordered collections called value groups. For details on
consuming value groups, see the documentation for the In type.
Constructors can send values into value groups by returning a result struct
tagged with `group:".."`.
type HandlerResult struct {
fx.Out
Handler Handler `group:"server"`
}
func NewHelloHandler() HandlerResult {
// ...
}
func NewEchoHandler() HandlerResult {
// ...
}
Any number of constructors may provide values to this named collection, but
the ordering of the final collection is unspecified. Keep in mind that
value groups require parameter and result structs to use fields with
different types: if a group of constructors each returns type T, parameter
structs consuming the group must use a field of type []T.
type Printer ¶
type Printer interface {
Printf(string, ...interface{})
}
Printer is the interface required by Fx's logging backend. It's implemented
by most loggers, including the one bundled with the standard library.
type ShutdownOption ¶
added in
v1.9.0
type ShutdownOption interface {
// contains filtered or unexported methods
}
ShutdownOption provides a way to configure properties of the shutdown
process. Currently, no options have been implemented.
type Shutdowner ¶
added in
v1.9.0
type Shutdowner interface {
Shutdown(...ShutdownOption) error
}
Shutdowner provides a method that can manually trigger the shutdown of the
application by sending a signal to all open Done channels. Shutdowner works
on applications using Run as well as Start, Done, and Stop. The Shutdowner is
provided to all Fx applications.
Source Files
¶
View all Source files
annotated.go
app.go
doc.go
extract.go
inout.go
lifecycle.go
populate.go
shutdown.go
version.go
Directories
¶
Show internal
Expand all
Path
Synopsis
fxtest
internal
fxlog
fxlog/foovendor
fxlog/sample.git
fxreflect
lifecycle
Click to show internal directories.
Click to hide internal directories.
Why Go
Use Cases
Case Studies
Get Started
Playground
Tour
Stack Overflow
Help
Packages
Standard Library
Sub-repositories
About Go Packages
About
Download
Blog
Issue Tracker
Release Notes
Brand Guidelines
Code of Conduct
Connect
GitHub
Slack
r/golang
Meetup
Golang Weekly
Copyright
Terms of Service
Privacy Policy
Report an Issue
Theme Toggle
Shortcuts Modal
Jump to
Close
Keyboard shortcuts
? : This menu
/ : Search site
f or F : Jump to
y or Y
: Canonical URL
Close
go.dev uses cookies from Google to deliver and enhance the quality of its services and to
analyze traffic. Learn more.
Okay
Get started with Fx | Fx
Get started with Fx | Fx
Fx
Guide
API Reference
(opens new window)
GitHub
(opens new window)
Guide
API Reference
(opens new window)
GitHub
(opens new window) Get Started Create a minimal applicationAdd an HTTP serverRegister a handlerAdd a loggerDecouple registrationRegister another handlerRegister many handlersConclusionIntroductionConcepts Features FAQCommunity Release notes # Get started with Fx This introduces you to the basics of Fx.
In this tutorial you will: start an empty application add an HTTP server to it register a handler with the server add logging to your application refactor to loosen coupling to your handler add another handler to the server generalize your implementation First, get set up for the rest of the tutorial. Start a new empty project. mkdir fxdemo
cd fxdemo
go mod init example.com/fxdemo
Install the latest version of Fx. go get go.uber.org/fx@latest
Now begin by creating a minimal application. Edit this page (opens new window) Last Updated: 2/20/2024, 4:15:55 PM