imtoken钱包下载 推荐|go fx

作者: imtoken钱包下载 推荐
2024-03-07 18:59:59

深入解析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 编程​赞同 24​​7 条评论​分享​喜欢​收藏​申请转载​文章被以下专栏收录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

Twitter

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

Twitter

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