go fx
深入解析go依賴(lài)注入庫(kù)go.uber.org/fx - 知乎
深入解析go依賴(lài)注入庫(kù)go.uber.org/fx - 知乎首發(fā)于go 開(kāi)源框架源碼解析切換模式寫(xiě)文章登錄/注冊(cè)深入解析go依賴(lài)注入庫(kù)go.uber.org/fx楊桃不愛(ài)程序員游戲行業(yè)服務(wù)器go 語(yǔ)言開(kāi)發(fā)后面更新采用肝一篇go官方源碼,肝一篇框架源碼形式,傷肝->護(hù)肝,如果你喜歡就點(diǎn)個(gè)贊吧。官方源碼比較傷肝(* ̄︶ ̄)。1依賴(lài)注入初識(shí)依賴(lài)注入來(lái)自開(kāi)源項(xiàng)目Grafana 的源碼,該項(xiàng)目框架采用依賴(lài)注入方式對(duì)各結(jié)構(gòu)體字段進(jìn)行賦值。DI 依賴(lài)注入包為https://github.com/facebookarchive/inject,后面我會(huì)專(zhuān)門(mén)介紹這個(gè)包依賴(lài)注入的原理。不過(guò)今天的主角是它:https://github.com/uber-go/fx。該包統(tǒng)一采用構(gòu)造函數(shù)Newxx()形式進(jìn)行依賴(lài)注入,對(duì)比與inject ,我認(rèn)為比較好的點(diǎn):采用Newxx()形式顯示聲明,更利于構(gòu)造單元測(cè)試采用Newxx()能更直觀(guān),表明我這個(gè)對(duì)象需要什么,inject 后面是tag,與結(jié)構(gòu)體字段混在一起。有時(shí)我們需要另一個(gè)對(duì)象,但不希望它出現(xiàn)在結(jié)構(gòu)體字段里面來(lái)看看我們自己給結(jié)構(gòu)體賦值怎么做假設(shè)我們一個(gè)對(duì)象需要b對(duì)象賦值,b 對(duì)象需要c 對(duì)象賦值,那么我們?cè)撨@么寫(xiě)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() {
//我們需要一個(gè)a
b:=NewB(NewC())
a:=NewA(b)
_=a
PrintA(a)
}
func PrintA(a* A) {
fmt.Println(*a)
}
?
如果選擇依賴(lài)注入呢,實(shí)際情況可能更加復(fù)雜,如果有更好的方式,那么一定是DI 依賴(lài)注入了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項(xiàng)目實(shí)踐例子,附上github地址:https://github.com/yangtaolirong/fx-demo,大家可以根據(jù)自己需求進(jìn)行優(yōu)化。2使用New()該函數(shù)時(shí)創(chuàng)建一個(gè)依賴(lài)注入實(shí)例option 的結(jié)構(gòu)// 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 必須使用下面的幾個(gè)方法進(jìn)行生成,來(lái)看個(gè)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()該函數(shù)將被依賴(lài)的對(duì)象的構(gòu)造函數(shù)傳進(jìn)去,傳進(jìn)去的函數(shù)必須是個(gè)待返回值的函數(shù)指針fx.Provide(NewGay)fx.Provide(NewGirl)Invoke()該函數(shù)將函數(shù)依賴(lài)的對(duì)象作為參數(shù)傳進(jìn)函數(shù)然后調(diào)用函數(shù)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()該函數(shù)直接提供被依賴(lài)的對(duì)象。不過(guò)這個(gè)supply 不能提供一個(gè)接口func main() {
invoke:= func(gay* Gay) {
fmt.Println(gay.Girl)
}
girl:=NewGirl() //直接提供對(duì)象
app:=fx.New(
fx.Provide(NewGay),
fx.Supply(girl),
fx.Invoke(invoke),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
不能提供接口類(lèi)型,比如我們使用Provide可以提供一個(gè)SayInterface類(lèi)型的接口,該代碼運(yùn)行不會(huì)報(bào)錯(cuò),但我們換成supply 以后就會(huì)有問(wèn)題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)
}
}
?
通過(guò)supply 提供就會(huì)報(bào)錯(cuò)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)
}
}
?
錯(cuò)誤: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?)
?原因我會(huì)在后面分析,反正是識(shí)別成了結(jié)構(gòu)體真正的類(lèi)型而不是接口類(lèi)型,平時(shí)在使用中,也是一個(gè)坑Populate()該函數(shù)將通過(guò)容器內(nèi)值外面的變量進(jìn)行賦值func main() {
invoke:= func(gay *Gay) {
?
}
var gay *Gay //定義一個(gè)對(duì)象,值為nil
app:=fx.New(
fx.Provide(NewGirl),
fx.Provide(NewGay),
fx.Invoke(invoke),
fx.Populate(&gay),//調(diào)用Populate,這里必須是指針,因?yàn)槭峭ㄟ^(guò)*target 來(lái)給元素賦值的
)
fmt.Println(gay) //&{0xc00008c680},將NewGay返回的對(duì)象放進(jìn)var定義的變量里面了
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
原理將傳進(jìn)來(lái)的參數(shù),換成函數(shù),參數(shù)為target,函數(shù)結(jié)果為類(lèi)似下面這種類(lèi)型,最后轉(zhuǎn)換成invoke類(lèi)型進(jìn)行調(diào)用// Build a function that looks like:
//
// func(t1 T1, t2 T2, ...) {
// *targets[0] = t1
// *targets[1] = t2
// [...]
// }
//
下面是函數(shù)實(shí)現(xiàn)// 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 */) //制造函數(shù)的類(lèi)型
fn := reflect.MakeFunc(fnType, func(args []reflect.Value) []reflect.Value {//制造函數(shù)
for i, arg := range args {
reflect.ValueOf(targets[i]).Elem().Set(arg)
}
return nil
})
return Invoke(fn.Interface()) //invoke選項(xiàng)
}
reflect.FuncOf該函數(shù)作用是通過(guò)指定的參數(shù)類(lèi)型和返回類(lèi)型創(chuàng)造一個(gè)函數(shù),共有3個(gè)參數(shù),variadic代表是不是可選參數(shù)FuncOf(in, out []Type, variadic bool) Typereflect.MakeFunc代表按照什么函數(shù)類(lèi)型制造函數(shù),其中第二個(gè)參數(shù)是個(gè)回調(diào)函數(shù),代表函數(shù)的傳參值和返回值,意思是將函數(shù)傳進(jìn)來(lái)的參數(shù)值賦值給Populate傳進(jìn)來(lái)的值A(chǔ)nnotated http://fx.inannotated提供高級(jí)功能,讓相同的對(duì)象按照tag能夠賦值到一個(gè)結(jié)構(gòu)體上面,結(jié)構(gòu)體必須內(nèi)嵌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,下面這種寫(xiě)法是可以的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,不能識(shí)別了,因?yàn)槭悄涿?/p>
),
?
)
?
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
group寫(xiě)多個(gè),用","分開(kāi)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)
}
}
?
錯(cuò)誤的寫(xiě)法,Group和Name 是不能同時(shí)存在的fx.Annotated{
Target: func() *Girl { return &Girl{Name: "波多"} },
Group: "actor,beauty",
Name:"波多"
},
當(dāng)返回切片時(shí),需要在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會(huì)將當(dāng)前結(jié)構(gòu)體的字段按名字輸出,相當(dāng)于fx.Annotated{
Target: func() *Girl { return &Girl{Name: "海翼"} },
Name: "海翼",
},
//或者
fx.Annotated{
Target: func() *Girl { return &Girl{Name: "杏梨"} },
Group: "actor",
},
所以在另一個(gè)結(jié)構(gòu)體寫(xiě)上http://fx.in 就能按名字接收到了type Gay struct {
fx.Out
Girl1 * Girl `name:"波多"`
}
type Gay1 struct {
fx.Out
Girl1 * Girl `name:"倉(cāng)井"`
}
type Man struct {
fx.In
Girl1 * Girl `name:"波多"`
Girl2 * Girl `name:"倉(cāng)井"`
}
func NewGay()Gay {
return Gay{
Girl1:&Girl{Name: "波多"},
}
}
func NewGay1()Gay1 {
return Gay1{
Girl1:&Girl{Name: "倉(cāng)井"},
}
}
func main() {
invoke:= func(man Man) {
fmt.Println(man.Girl1.Name)//波多
fmt.Println(man.Girl2.Name) //倉(cāng)井
}
?
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
//創(chuàng)建和初始化app 實(shí)例,并且是立即執(zhí)行注冊(cè)和調(diào)用的
// 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) //獲取日志實(shí)例
?
app := &App{//創(chuàng)建app 實(shí)例
// 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, //啟動(dòng)超時(shí)時(shí)間
stopTimeout: DefaultTimeout, //停止超時(shí)時(shí)間
}
?
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{ //初始生命周期函數(shù)
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( //創(chuàng)建container
dig.DeferAcyclicVerification(),
dig.DryRun(app.validate),
)
?
for _, p := range app.provides { //app.provides 通過(guò)opt 已經(jīng)初始化了,所以這就是調(diào)用fx.Provide()里面的構(gòu)造函數(shù)
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這個(gè)對(duì)象提供出去
Stack: frames,
})
//提供shutdowner,和dotGraph這兩個(gè)實(shí)例
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 { //執(zhí)行調(diào)用
app.err = err
?
if dig.CanVisualizeError(err) {//如果錯(cuò)誤可以可視化,就走下面邏輯打印
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 函數(shù)傳進(jìn)來(lái)的Option結(jié)構(gòu),必須要實(shí)現(xiàn)對(duì)app 初始化的方法apply(*App),要實(shí)現(xiàn)打印接口fmt.Stringer方法,現(xiàn)在做框架傳配置幾乎都采用這種套路了, 優(yōu)雅的傳配置。// 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類(lèi)型,生成相應(yīng)的選項(xiàng)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該函數(shù)將會(huì)執(zhí)行函數(shù)調(diào)用,fx.Inovke()添加invoke 函數(shù)調(diào)用// 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 { //循環(huán)遍歷invokes函數(shù)
if err := app.executeInvoke(i); err != nil {
return err
}
}
?
return nil
}
app.executeInvoke//執(zhí)行調(diào)用
func (app *App) executeInvoke(i invoke) (err error) {
fn := i.Target
fnName := fxreflect.FuncName(fn) //獲取調(diào)用的函數(shù)名
//日志相關(guān)
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
})
}()
//對(duì)fn 進(jìn)行校驗(yàn),如果還是Option類(lèi)型,說(shuō)明是錯(cuò)誤了,報(bào)錯(cuò)
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) //執(zhí)行容器的調(diào)用方法Invoke
}
dig.Containercontainer.Provide該函數(shù)作用是將構(gòu)造函數(shù)賦值給容器,在這之前還要做一系列檢查// 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 { //構(gòu)造函數(shù)不能為nil
return errors.New("can't provide an untyped nil")
}
if ctype.Kind() != reflect.Func { //構(gòu)造函數(shù)必須是函數(shù)
return errf("must provide constructor function, got %v (type %v)", constructor, ctype)
}
?
var options provideOptions
for _, o := range opts {
o.applyProvideOption(&options) //如果有選項(xiàng)就應(yīng)用選項(xiàng)
}
if err := options.Validate(); err != nil {
return err
}
//調(diào)用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,
},
) //創(chuàng)建1個(gè)node節(jié)點(diǎn)
if err != nil {
return err
}
//驗(yàn)證結(jié)果
keys, err := c.findAndValidateResults(n)
if err != nil {
return err
}
?
ctype := reflect.TypeOf(ctor) //獲取構(gòu)造函數(shù)的反射類(lèi)型
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 哪些節(jié)點(diǎn)能夠提供
?
if c.deferAcyclicVerification {
continue
}
//驗(yàn)證是否循環(huán)依賴(lài)
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節(jié)點(diǎn)newNodefunc newNode(ctor interface{}, opts nodeOptions) (*node, error) {
cval := reflect.ValueOf(ctor) //獲取構(gòu)造函數(shù)的反射值
ctype := cval.Type()//獲取構(gòu)造函數(shù)的反射類(lèi)型,獲取構(gòu)造函數(shù)的指針
cptr := cval.Pointer()
?
params, err := newParamList(ctype)//獲取參數(shù)列表
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,//構(gòu)造函數(shù)
ctype: ctype, //構(gòu)造函數(shù)類(lèi)型
location: digreflect.InspectFunc(ctor),
id: dot.CtorID(cptr), //用指針地址作為節(jié)點(diǎn)的id
paramList: params,//構(gòu)造函數(shù)的參數(shù)
resultList: results,//構(gòu)造函數(shù)的結(jié)果
}, 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 函數(shù)的參數(shù)
if ctype.IsVariadic() { //如果函數(shù)是可選參數(shù),我們跳過(guò)最后一個(gè)參數(shù),從這里可以知道,invoke 函數(shù)后面寫(xiě)可選參數(shù),是可以的
// 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)) //獲取函數(shù)的參數(shù)列表
if err != nil {
return pl, errf("bad argument %d", i+1, err)
}
pl.Params = append(pl.Params, p) //添加封裝后的參數(shù)
}
?
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 {
//參數(shù)如果是out 類(lèi)型則報(bào)錯(cuò)
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 類(lèi)型,創(chuàng)建newParamObject類(lèi)型
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:
//創(chuàng)建paramSingle類(lèi)型
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++ { //循環(huán)遍歷構(gòu)造函數(shù)的輸出參數(shù)
t := ctype.Out(i)//獲取參數(shù)
if isError(t) {//如果是錯(cuò)誤類(lèi)型,將這行結(jié)果索引賦值為-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 //添加結(jié)果類(lèi)型,注意這里沒(méi)有用i,說(shuō)明是有效的返回類(lèi)型才會(huì)添加
resultIdx++
}
?
return rl, nil
}
newResult// newResult builds a result from the given type.
func newResult(t reflect.Type, opts resultOptions) (result, error) {
switch {
//如果該類(lèi)型內(nèi)嵌fx.IN,那么就報(bào)錯(cuò)
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)
//是錯(cuò)誤也返回,不能返回錯(cuò)誤類(lèi)型在構(gòu)造函數(shù)里面
case isError(t):
return nil, errf("cannot return an error here, return it from the constructor instead")
//結(jié)構(gòu)體如果內(nèi)嵌fx.Out,返回ResultObject類(lèi)型
case IsOut(t):
return newResultObject(t, opts)
//結(jié)果類(lèi)型內(nèi)嵌必須是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)
//結(jié)果對(duì)象不能是指針
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: //如果構(gòu)造函數(shù)是group類(lèi)型,則創(chuàng)建resultGrouped類(lèi)型
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(shù).Flatten,那么這個(gè)構(gòu)造函數(shù)返回值必須是切片類(lèi)型
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:
//返回單個(gè)參數(shù)類(lèi)型
return resultSingle{Type: t, Name: opts.Name}, nil
}
}
根據(jù)構(gòu)造函數(shù)返回的每個(gè)參數(shù)類(lèi)型和選項(xiàng)創(chuàng)建一個(gè)result對(duì)象可見(jiàn)內(nèi)嵌fx.Out 返回必須是個(gè)對(duì)象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)//遞歸調(diào)用walkResult,傳入?yún)?shù)為返回結(jié)構(gòu)體的字段
}
}
case resultList:
w := v
for i, r := range res.Results {
if v := w.AnnotateWithPosition(i); v != nil {
walkResult(r, v)//遞歸調(diào)用walkResult,傳入?yún)?shù)為切片的每個(gè)值
}
}
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 存在,并且返回值類(lèi)型是resultSingle類(lèi)型,說(shuō)明該提供依賴(lài)以及存在了,根name和group 稍微有區(qū)別
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 類(lèi)型直接賦值就行了,代表該類(lèi)型提供了值
k := key{group: r.Group, t: r.Type}
cv.keyPaths[k] = path
}
?
return cv
}
container.InvokeInvoke會(huì)在初始化依賴(lài)后調(diào)用,這個(gè)函數(shù)的任何參數(shù)都會(huì)被認(rèn)為是它的依賴(lài),這個(gè)依賴(lài)被初始化沒(méi)有順序,invoke 調(diào)用可能會(huì)有錯(cuò)誤,這個(gè)錯(cuò)誤將會(huì)被返回// 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) //獲取函數(shù)類(lèi)型
if ftype == nil { //判斷是不是nil
return errors.New("can't invoke an untyped nil")
}
if ftype.Kind() != reflect.Func { //判斷是不是函數(shù)
return errf("can't invoke non-function %v (type %v)", function, ftype)
}
?
pl, err := newParamList(ftype)//獲取函數(shù)參數(shù)列表
if err != nil {
return err
}
?
if err := shallowCheckDependencies(c, pl); err != nil { //檢查依賴(lài)
return errMissingDependencies{
Func: digreflect.InspectFunc(function),
Reason: err,
}
}
?
if !c.isVerifiedAcyclic {//沒(méi)有驗(yàn)證循環(huán),驗(yàn)證循環(huán)
if err := c.verifyAcyclic(); err != nil {
return err
}
}
?
args, err := pl.BuildList(c)//將參數(shù)賦值,返回賦值后的參數(shù)
if err != nil {
return errArgumentsFailed{
Func: digreflect.InspectFunc(function),
Reason: err,
}
}
returned := c.invokerFn(reflect.ValueOf(function), args)//調(diào)用函數(shù)結(jié)果
if len(returned) == 0 {
return nil
}
if last := returned[len(returned)-1]; isError(last.Type()) {//如果最后一個(gè)結(jié)果是錯(cuò)誤,會(huì)將此錯(cuò)誤進(jìn)行返回
if err, _ := last.Interface().(error); err != nil {
return err
}
}
?
return nil
}
shallowCheckDependencies檢查依賴(lài)是否缺少,比如func( a A),如果A 這種類(lèi)型的對(duì)象在container 里面找不到,也就是說(shuō)構(gòu)造函數(shù)沒(méi)有提供,那么在這里將會(huì)報(bào)錯(cuò)?
// 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
}
}
校驗(yàn)循環(huán),如果沒(méi)有校驗(yàn)過(guò)循環(huán),就校驗(yàn)循環(huán)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
}
檢驗(yàn)循環(huán)的原理是遞歸遍歷該參數(shù)的提供者,如果該提供者出現(xiàn)過(guò)說(shuō)明出現(xiàn)了循環(huán),例如a ->b->c ->d->a ,d 的提供者是a ,但a 已經(jīng)出現(xiàn)過(guò)了,所以出現(xiàn)了循環(huán)pl.BuildList該函數(shù)通過(guò)容器,查找到invoke 函數(shù)需要的參數(shù)值,然后通過(guò)下面的invokerFn進(jìn)行調(diào)用。該函數(shù)返回有序的結(jié)果列表// 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
}
對(duì)象不用的參數(shù)p,Build 表現(xiàn)不也一樣,這里以paramSingle為例func (ps paramSingle) Build(c containerStore) (reflect.Value, error) {
if v, ok := c.getValue(ps.Name, ps.Type); ok { //從容器里面查找該名字和類(lèi)型的參數(shù),如果查到了就返回
return v, nil
}
//如果上面一步?jīng)]有直接獲取到,那么就查找能提供這個(gè)key 的節(jié)點(diǎn),ps.Name, ps.Type組合成的結(jié)構(gòu)體為key
providers := c.getValueProviders(ps.Name, ps.Type)
if len(providers) == 0 { //如果提供的節(jié)點(diǎn)找不到,如果參數(shù)是可選的,那么直接返回零值
if ps.Optional {
return reflect.Zero(ps.Type), nil
}
//如果沒(méi)找到說(shuō)明沒(méi)有這個(gè)類(lèi)型直接返回
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) //再?lài)L試找,如果查找到這里,那么一定是有值得
return v, nil
}
Callcall 調(diào)用節(jié)點(diǎn)的構(gòu)造函數(shù),獲得結(jié)果,然后存儲(chǔ)在該節(jié)點(diǎn)里面,這個(gè)過(guò)程可能會(huì)出現(xiàn)遞歸,比如a 依賴(lài)b,b 的參數(shù)依賴(lài)c,就會(huì)調(diào)用BuildList,一層一層往上找,找不到返回錯(cuò)誤,找到了,最后調(diào)用a 的構(gòu)造函數(shù)創(chuàng)建結(jié)果// 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 { //這里用來(lái)標(biāo)識(shí)該節(jié)點(diǎn)是否被call 過(guò)
return nil
}
?
if err := shallowCheckDependencies(c, n.paramList); err != nil {
return errMissingDependencies{
Func: n.location,
Reason: err,
}
}
?
args, err := n.paramList.BuildList(c) //一個(gè)遞歸過(guò)程,將該節(jié)點(diǎn)的參數(shù)進(jìn)行BuildList,獲取該節(jié)點(diǎn)的參數(shù)
if err != nil {
return errArgumentsFailed{
Func: n.location,
Reason: err,
}
}
?
receiver := newStagingContainerWriter()
//然后調(diào)用該節(jié)點(diǎn)的構(gòu)造函數(shù),將剛剛獲取的參數(shù)傳進(jìn)去進(jìn)行調(diào)用,獲取調(diào)用后的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)//將結(jié)果放入container
n.called = true
?
return nil
}
ExtractList ExtractList 將構(gòu)造函數(shù)獲取的結(jié)果賦值到節(jié)點(diǎn)的Resultsfunc (rl resultList) ExtractList(cw containerWriter, values []reflect.Value) error {
for i, v := range values { //循環(huán)遍歷結(jié)果值
if resultIdx := rl.resultIndexes[i]; resultIdx >= 0 {
rl.Results[resultIdx].Extract(cw, v) //將結(jié)果值賦值到containerWriter里面去
continue
}
//調(diào)用結(jié)構(gòu)包含err,直接返回
if err, _ := v.Interface().(error); err != nil {
return err
}
}
?
return nil
}
Extract就是給containerWriter賦值,然后最后調(diào)用 receiver.Commit(c)將值復(fù)制到容器里面去func (rs resultSingle) Extract(cw containerWriter, v reflect.Value) {
cw.setValue(rs.Name, rs.Type, v)
}
invokerFn默認(rèn)的調(diào)用,就是通過(guò)反射獲取invoke 函數(shù)的// 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)
}
項(xiàng)目實(shí)踐下面我們將通過(guò)搭建一個(gè)http 服務(wù)器在項(xiàng)目中實(shí)踐來(lái)練習(xí)fx 的使用,項(xiàng)目目錄結(jié)構(gòu)server/main.gopackage main
?
import (
"server/pkg/config"
"server/pkg/log"
"server/server"
)
?
func main() {
srv:=server.NewServer() //創(chuàng)建一個(gè)服務(wù)器
srv.Provide(
log.GetLogger, //依賴(lài)注入Logger
config.NewConfig,//依賴(lài)注入配置文件
)
srv.Run()//運(yùn)行服務(wù)
}
?
?
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,參考我的文章,專(zhuān)門(mén)講這個(gè)原理
app *fx.App //fx 實(shí)例
provides []interface{}
invokes []interface{}
supplys []interface{}
httSrv *HttpServer //該http server 可以換成fibber gin 之類(lèi)的
}
?
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 實(shí)例賦值
fx.NopLogger,//禁用fx 默認(rèn)logger
)
srv.group.Go(srv.httSrv.Run) //啟動(dòng)http 服務(wù)器
err:=srv.group.Wait() //等待子協(xié)程退出
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) //暫時(shí)不啟用文件
case "file":
default:
writeSyncer=zapcore.NewMultiWriteSyncer(os.Stdout) //暫時(shí)不啟用文件
}
?
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"啟動(dòng)服務(wù)器: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,可以看見(jiàn)打印hello編輯于 2021-10-24 23:05Go 語(yǔ)言Go 編程?贊同 24??7 條評(píng)論?分享?喜歡?收藏?申請(qǐng)轉(zhuǎn)載?文章被以下專(zhuān)欄收錄go 開(kāi)源框架源碼解析go 各種優(yōu)秀開(kāi)源框架源
解析 Golang 依賴(lài)注入經(jīng)典解決方案 uber/fx 理論篇 - 掘金
解析 Golang 依賴(lài)注入經(jīng)典解決方案 uber/fx 理論篇 - 掘金
首頁(yè) 首頁(yè)
沸點(diǎn)
課程
直播
活動(dòng)
競(jìng)賽
商城
APP
插件 搜索歷史
清空
創(chuàng)作者中心
寫(xiě)文章 發(fā)沸點(diǎn) 寫(xiě)筆記 寫(xiě)代碼 草稿箱 創(chuàng)作靈感
查看更多
會(huì)員
登錄
注冊(cè)
解析 Golang 依賴(lài)注入經(jīng)典解決方案 uber/fx 理論篇
ag9920
2022-10-12
1,720
持續(xù)創(chuàng)作,加速成長(zhǎng)!這是我參與「掘金日新計(jì)劃 · 10 月更文挑戰(zhàn)」的第8天,點(diǎn)擊查看活動(dòng)詳情
開(kāi)篇
今天繼續(xù)我們的【依賴(lài)注入開(kāi)源解決方案系列】, Dependency Injection 業(yè)界的開(kāi)源庫(kù)非常多,大家可以憑借自己的喜好也業(yè)務(wù)的復(fù)雜度來(lái)選型?;?github star 數(shù)量以及方案的全面性,易用性上。推薦這兩個(gè):
1.【代碼生成】派系推薦大家用 wire, 做的事情非常輕量級(jí),省下大家手寫(xiě)代碼的負(fù)擔(dān),沒(méi)有太多 DI 工具帶來(lái)的結(jié)構(gòu)性改造;
2.【反射】派系推薦大家用 uber/fx,功能非常強(qiáng)大,很全面,也比較符合直覺(jué)。
二者都需要顯式聲明依賴(lài),這一點(diǎn)對(duì)程序的可讀性是好事,兩個(gè)庫(kù)的 star 也都非常多。建議大家有興趣的話(huà)研讀一下。不管是 codegen 還是 reflect(結(jié)合 interface{},泛型)都是 Golang 學(xué)習(xí)體系中必須的能力,否則很難實(shí)現(xiàn)通用的一些能力。
今天我們來(lái)看看 uber/fx 這個(gè)反射派系的經(jīng)典之作,這是 uber 家基于 dig 的又一步進(jìn)化。
uber/fx
Fx is a dependency injection system for Go.
fx 是 uber 2017 年開(kāi)源的依賴(lài)注入解決方案,不僅僅支持常規(guī)的依賴(lài)注入,還支持生命周期管理。
從官方的視角看,fx 能為開(kāi)發(fā)者提供的三大優(yōu)勢(shì):
代碼復(fù)用:方便開(kāi)發(fā)者構(gòu)建松耦合,可復(fù)用的組件;
消除全局狀態(tài):Fx 會(huì)幫我們維護(hù)好單例,無(wú)需借用 init() 函數(shù)或者全局變量來(lái)做這件事了;
經(jīng)過(guò)多年 Uber 內(nèi)部驗(yàn)證,足夠可信。
我們從 uber-go/fx 看到的是 v1 的版本,fx 是遵循 SemVer 規(guī)范的,保障了兼容性,這一點(diǎn)大家可以放心。
從劣勢(shì)的角度分析,其實(shí) uber/fx 最大的劣勢(shì)還是大量使用反射,導(dǎo)致項(xiàng)目啟動(dòng)階段會(huì)需要一些性能消耗,但這一般是可以接受的。如果對(duì)性能有高要求,建議還是采取 wire 這類(lèi) codegen 的依賴(lài)注入解法。
目前市面上對(duì) Fx 的介紹文章并不多,筆者在學(xué)習(xí)的時(shí)候也啃了很長(zhǎng)時(shí)間官方文檔,這一點(diǎn)有好有壞。的確,再多的例子,再多的介紹,也不如一份完善的官方文檔更有力。但同時(shí)也給初學(xué)者帶來(lái)較高的門(mén)檻。
今天這篇文章希望從一個(gè)開(kāi)發(fā)者的角度,帶大家理解 Fx 如何使用。
添加 fx 的依賴(lài)需要用下面的命令:
go get go.uber.org/fx@v1
后面我們會(huì)有專(zhuān)門(mén)的一篇文章,拿一個(gè)實(shí)戰(zhàn)項(xiàng)目來(lái)給大家展示,如何使用 Fx,大家同時(shí)也可以參考官方 README 中的 Getting Started 來(lái)熟悉。
下面一步一步來(lái),我們先來(lái)看看 uber/fx 中的核心概念。
provider 聲明依賴(lài)關(guān)系
在我們的業(yè)務(wù)服務(wù)的聲明周期中,對(duì)于各個(gè) module 的初始化應(yīng)該基于我們的 dependency graph 來(lái)合理進(jìn)行。先初始化無(wú)外部依賴(lài)的對(duì)象,隨后基于這些對(duì)象,初始化對(duì)它們有依賴(lài)的對(duì)象。
Provider 就是我們常說(shuō)的構(gòu)造器,能夠提供對(duì)象的生成邏輯。在 Fx 啟動(dòng)時(shí)會(huì)創(chuàng)建一個(gè)容器,我們需要將業(yè)務(wù)的構(gòu)造器傳進(jìn)來(lái),作為 Provider。類(lèi)似下面這樣:
app = fx.New(
fx.Provide(newZapLogger),
fx.Provide(newRedisClient),
fx.Provide(newMeaningOfLifeCacheRedis),
fx.Provide(newMeaningOfLifeHandler),
)
這里面的 newXXX 函數(shù),就是我們的構(gòu)造器,類(lèi)似這樣:
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
我們只需要通過(guò) fx.Provide 方法傳入進(jìn)容器,就完成了將對(duì)象提供出去的使命。隨后 fx 會(huì)在需要的時(shí)候調(diào)用我們的 Provider,生成單例對(duì)象使用。
當(dāng)然,構(gòu)造器不光是這種沒(méi)有入?yún)⒌?。還有一些對(duì)象是需要顯式的傳入依賴(lài):
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 也可以成為別人的依賴(lài)。
這些,我們通通不用關(guān)心!
fx 會(huì)自己通過(guò)反射,搞明白哪個(gè) Provider 需要什么,能提供什么。構(gòu)建出來(lái)整個(gè) 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),
}
}
作為開(kāi)發(fā)者,我們只需要保證,所有我們需要的依賴(lài),都通過(guò) fx.Provide 函數(shù)提供即可。另外需要注意,雖然上面我們是每個(gè) fx.Provide,都只包含一個(gè)構(gòu)造器,實(shí)際上他是支持多個(gè)構(gòu)造器的。
module 模塊化組織依賴(lài)
// 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 也是經(jīng)典的概念。實(shí)際上我們?cè)谶M(jìn)行軟件開(kāi)發(fā)時(shí),分層分包是不可避免的。而 fx 也是基于模塊化編程。使用 module 能夠幫助我們更方便的管理依賴(lài):
// 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 是一個(gè)可導(dǎo)出的變量,包含了一組 fx.Option,這里包含了各個(gè) Provider。
這樣,我們就不必要在容器初始化時(shí)傳入那么多 Provider 了,而是每個(gè) Module 干好自己的事即可。
func main() {
fx.New(
fx.Provide(http.NewServeMux),
fx.Invoke(server.New),
fx.Invoke(registerHooks),
loggerfx.Module,
).Run()
}
lifecycle 給應(yīng)用生命周期加上鉤子
// 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 定義的一個(gè)接口。我們可以對(duì) fx.Lifecycle 進(jìn)行 append 操作,增加鉤子函數(shù),這里就可以支持我們訂閱一些指定行為,如 OnStart 和 OnStop。
如果執(zhí)行某個(gè) OnStart 鉤子時(shí)出現(xiàn)錯(cuò)誤,應(yīng)用會(huì)立刻停止后續(xù)的 OnStart,并針對(duì)此前已經(jīng)執(zhí)行過(guò) OnStart 的鉤子執(zhí)行對(duì)應(yīng)的 OnStop 用于清理資源。
這里 fx 加上了 15 秒的超時(shí)限制,通過(guò) context.Context 實(shí)現(xiàn),大家記得控制好自己的鉤子函數(shù)執(zhí)行時(shí)間。
invoker 應(yīng)用的啟動(dòng)器
provider 是懶加載的,僅僅 Provide 出來(lái)我們的構(gòu)造器,是不會(huì)當(dāng)時(shí)就觸發(fā)調(diào)用的,而 invoker 則能夠直接觸發(fā)業(yè)務(wù)提供的函數(shù)運(yùn)行。并且支持傳入一個(gè) fx.Lifecycle 作為入?yún)?,業(yè)務(wù)可以在這里 append 自己想要的 hook。
假設(shè)我們有一個(gè) http server,希望在 fx 應(yīng)用啟動(dòng)的時(shí)候同步開(kāi)啟。這個(gè)時(shí)候就需要兩個(gè)入?yún)ⅲ?/p>
fx.Lifecycle
我們的主依賴(lài)(通常是對(duì)服務(wù)接口的實(shí)現(xiàn),一個(gè) handler)
我們將這里的邏輯封裝起來(lái),就可以作為一個(gè) invoker 讓 Fx 來(lái)調(diào)用了??聪率纠a:
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),
)
這樣在創(chuàng)建容器時(shí),我們的 runHttpServer 就會(huì)被調(diào)用,進(jìn)而注冊(cè)了服務(wù)啟動(dòng)的邏輯。這里我們需要一個(gè) MeaningOfLifeHandler,F(xiàn)x 會(huì)觀(guān)察到這一點(diǎn),進(jìn)而到 Provider 里面挨個(gè)找依賴(lài),每個(gè)類(lèi)型對(duì)應(yīng)一個(gè)單例對(duì)象,通過(guò)懶加載的方式獲取到 MeaningOfLifeHandler 的所有依賴(lài),以及子依賴(lài)。
其實(shí) Invoker 更多意義上看,像是一個(gè)觸發(fā)器。
我們可以有很多 Provider,但什么時(shí)候去調(diào)用這些函數(shù),生成依賴(lài)呢?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
最后,有了一個(gè)通過(guò) fx.New 生成的 fx 應(yīng)用,我們就可以通過(guò) Start 方法來(lái)啟動(dòng)了:
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)
}
}
當(dāng)然,有了一個(gè) fx 應(yīng)用后,我們可以直接 fx.New().Run() 來(lái)啟動(dòng),也可以隨后通過(guò) app.Start(ctx) 方法啟動(dòng),配合 ctx 的取消和超時(shí)能力。二者皆可。
fx.In 封裝多個(gè)入?yún)?/p>
當(dāng)構(gòu)造函數(shù)參數(shù)過(guò)多的時(shí)候,我們可以使用 fx.In 來(lái)統(tǒng)一注入,而不用在構(gòu)造器里一個(gè)個(gè)加參數(shù):
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 封裝多個(gè)出參
和 In 類(lèi)似,有時(shí)候我們需要返回多個(gè)參數(shù),這時(shí)候一個(gè)個(gè)寫(xiě)顯然比較笨重。我們可以用 fx.Out 的能力用結(jié)構(gòu)體來(lái)封裝:
type Result struct {
fx.Out
Logger *log.Logger
Handler http.Handler
}
func NewResult() Result {
// logger := xxx
// handler := xxx
return Result {
Logger: logger,
Handler: handler,
}
}
基于同類(lèi)型提供多種實(shí)現(xiàn)
By default, Fx applications only allow one constructor for each type.
Fx 應(yīng)用默認(rèn)只允許每種類(lèi)型存在一個(gè)構(gòu)造器,這種限制在一些時(shí)候是很痛的。
有些時(shí)候我們就是會(huì)針對(duì)一個(gè) interface 提供多種實(shí)現(xiàn),如果做不到,我們就只能在外面套一個(gè)類(lèi)型,這和前一篇文章中我們提到的 wire 里的處理方式是一樣的:
type RedisA *redis.Client
type RedisB *redis.Client
但這樣還是很笨重,有沒(méi)有比較優(yōu)雅的解決方案呢?
當(dāng)然有,要不 uber/fx 怎么能被稱(chēng)為一個(gè)功能全面的 DI 方案呢?
既然是同類(lèi)型,多個(gè)不同的值,我們可以給不同的實(shí)現(xiàn)命名來(lái)區(qū)分。進(jìn)而這涉及兩個(gè)部分:生產(chǎn)端 和 消費(fèi)端。
在提供依賴(lài)的時(shí)候,可以聲明它的名稱(chēng),進(jìn)而即便出現(xiàn)同類(lèi)型的其他依賴(lài),fx 也知道如何區(qū)分。
在獲取依賴(lài)的時(shí)候,也要指明我們需要的依賴(lài)的名稱(chēng)具體是什么,而不只是簡(jiǎn)單的明確類(lèi)型即可。
這里我們需要用到 fx.In 和 fx.Out 的能力。參照 官方文檔 我們來(lái)了解一下 fx 的解法:Named Values。
fx 支持開(kāi)發(fā)者聲明 name 標(biāo)簽,用來(lái)給依賴(lài)「起名」,類(lèi)似這樣: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 就知道,我們?nèi)?gòu)建 NewCommentGateway 的時(shí)候,傳入的 *sql.DB 需要是 rw 這個(gè)名稱(chēng)的。而此前ConnectToDatabase 已經(jīng)提供了這個(gè)名稱(chēng),同類(lèi)型的實(shí)例,所以依賴(lài)構(gòu)建成功。
使用起來(lái)非常簡(jiǎn)單,在我們對(duì) In 和 Out 的 wrapper 中聲明各個(gè)依賴(lài)的 name,也可以搭配 optional 標(biāo)簽使用。fx 支持任意多個(gè) name 的實(shí)例。
這里需要注意,同名稱(chēng)的生產(chǎn)端和消費(fèi)端的類(lèi)型必須一致,不能一個(gè)是 sql.DB 另一個(gè)是 *sql.DB。命名的能力只有在同類(lèi)型的情況下才有用處。
Annotate 注解器
Annotate lets you annotate a function's parameters and returns without you having to declare separate struct definitions for them.
注解器能幫我們修改函數(shù)的入?yún)⒑统鰠?,無(wú)需定義單獨(dú)的結(jié)構(gòu)體。fx 的這個(gè)能力非常強(qiáng)大,目前暫時(shí)沒(méi)有看到其他 DI 工具能做到這一點(diǎn)。
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
}
我們來(lái)看看如何用 Annotate 來(lái)添加 ParamTag, ResultTag 來(lái)實(shí)現(xiàn)同一個(gè) interface 多種實(shí)現(xiàn)。
// 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 接口,以及對(duì)應(yīng)的三種實(shí)現(xiàn):GoodDoer, BadDoer, UglyDoer,三種實(shí)現(xiàn)的構(gòu)造器返回值甚至都不需要是Doer,完全可以是自己的 struct 類(lèi)型。
這里還是不得不感慨 fx 強(qiáng)大的裝飾器能力。我們用一個(gè)簡(jiǎn)單的:
fx.Annotate(NewGoodDoer, fx.As(new(Doer)))
就可以對(duì)構(gòu)造器 NewGoodDoer 完成類(lèi)型轉(zhuǎn)換。
這里還可以寫(xiě)一個(gè) helper 函數(shù)簡(jiǎn)化一下處理:
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"),
)
與之相對(duì)的,提供依賴(lài)的時(shí)候我們用 ResultTag,消費(fèi)依賴(lài)的時(shí)候需要用 ParamTag。
func Do(good, bad, ugly Doer) {
// ...
}
fx.Invoke(
fx.Annotate(Do, fx.ParamTags(`name:"good"`, `name:"bad"`, `name:"ugly"`)),
)
這樣就無(wú)需通過(guò) fx.In 和 fx.Out 的封裝能力來(lái)實(shí)現(xiàn)了,非常簡(jiǎn)潔。
當(dāng)然,如果我們上面的返回值直接就是 interface,那么久不需要 fx.As 這一步轉(zhuǎn)換了。
func NewGateway(ro, rw *db.Conn) *Gateway { ... }
fx.Provide(
fx.Annotate(
NewGateway,
fx.ParamTags(`name:"ro" optional:"true"`, `name:"rw"`),
fx.ResultTags(`name:"foo"`),
),
)
和下面的實(shí)現(xiàn)是等價(jià)的:
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)}
})
這里需要注意存在兩個(gè)限制:
Annotate 不能應(yīng)用于包含 fx.In 和 fx.Out 的函數(shù),它的存在本身就是為了簡(jiǎn)化;
不能在一個(gè) Annotate 中多次使用同一個(gè)注解,比如下面這個(gè)例子會(huì)報(bào)錯(cuò):
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"`)
)
)
小結(jié)
這里是 uber/fx 的理論篇,我們了解了 fx 的核心概念和基礎(chǔ)用法。和 wire 一樣,它們都要求強(qiáng)制編寫(xiě)構(gòu)造函數(shù),有額外的編碼成本。但好處在于功能全面、設(shè)計(jì)比較優(yōu)雅,對(duì)業(yè)務(wù)代碼無(wú)侵入。
下一篇,我們會(huì)從實(shí)戰(zhàn)的角度,基于 cloudwego 社區(qū)的 Kitex 框架,看看怎么基于 uber/fx 實(shí)現(xiàn)優(yōu)雅的注入,敬請(qǐng)期待。
參考資料
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
粉絲 目錄 收起
開(kāi)篇
uber/fx
provider 聲明依賴(lài)關(guān)系
module 模塊化組織依賴(lài)
lifecycle 給應(yīng)用生命周期加上鉤子
invoker 應(yīng)用的啟動(dòng)器
fx.In 封裝多個(gè)入?yún)?/p>
fx.Out 封裝多個(gè)出參
基于同類(lèi)型提供多種實(shí)現(xiàn)
Annotate 注解器
小結(jié)
參考資料
相關(guān)推薦 golang中連接mongo數(shù)據(jù)庫(kù)并進(jìn)行操作 801閱讀 ?·? 0點(diǎn)贊go日志框架-zap 286閱讀 ?·? 0點(diǎn)贊Golang官方限流器庫(kù)詳解 2.6k閱讀 ?·? 21點(diǎn)贊Alibaba/IOC-golang 正式開(kāi)源 ——打造服務(wù)于go開(kāi)發(fā)者的IOC框架 944閱讀 ?·? 4點(diǎn)贊自我封裝的基于go-gin的web服務(wù)框架 2.3k閱讀 ?·? 7點(diǎn)贊
golang反射框架Fx - 簡(jiǎn)書(shū)
ng反射框架Fx - 簡(jiǎn)書(shū)登錄注冊(cè)寫(xiě)文章首頁(yè)下載APP會(huì)員IT技術(shù)golang反射框架Fx神奇的考拉關(guān)注贊賞支持golang反射框架Fx一、概述Fx是一個(gè)golang版本的依賴(lài)注入框架,它使得golang通過(guò)可重用、可組合的模塊化來(lái)構(gòu)建golang應(yīng)用程序變得非常容易,可直接在項(xiàng)目中添加以下內(nèi)容即可體驗(yàn)Fx效果。
import "github.com/uber-go/fx"
Fx是通過(guò)使用依賴(lài)注入的方式替換了全局通過(guò)手動(dòng)方式來(lái)連接不同函數(shù)調(diào)用的復(fù)雜度,也不同于其他的依賴(lài)注入方式,F(xiàn)x能夠像普通golang函數(shù)去使用,而不需要通過(guò)使用struct標(biāo)簽或內(nèi)嵌特定類(lèi)型。這樣使得Fx能夠在很多go的包中很好的使用。
接下來(lái)會(huì)提供一些Fx的簡(jiǎn)單demo,并說(shuō)明其中的一些定義。
生命周期
二、Fx應(yīng)用
1、一般步驟
按需定義一些構(gòu)造函數(shù):主要用于生成依賴(lài)注入的具體類(lèi)型
type FxDemo struct{
// 字段是可導(dǎo)出的,是由于golang的reflection特性決定: 必須能夠?qū)С銮铱蓪ぶ凡拍苓M(jìn)行設(shè)置
Name string
}
func NewFxDemo(){
return FxDemo{
Name: "hello, world",
}
}
、使用Provide將具體反射的類(lèi)型添加到container中
可以按需添加任意多個(gè)構(gòu)造函數(shù)
fx.Provide(NewFxDemo)
、使用Populate完成變量與具體類(lèi)型間的映射
var fx FxDemo
fx.Populate(fx)
、新建app對(duì)象(application容器包括定義注入變量、類(lèi)型、不同對(duì)象lifecycle等)
app := fx.New(
fx.Provide(NewFxDemo,), // 構(gòu)造函數(shù)可以任意多個(gè)
fx.Populate(new(FxDemo)),// 反射變量也可以任意多個(gè),并不需要和上面構(gòu)造函數(shù)對(duì)應(yīng)
)
app.Start(context.Background()) // 開(kāi)啟container
defer app.Stop(context.Background()) // 關(guān)閉container
、使用
fmt.Printf("the result is %s \n", fx.Name)
大致的使用步驟就如下。下面會(huì)給出一些完整的demo
2、簡(jiǎn)單demo
將io.reader與具體實(shí)現(xiàn)類(lèi)關(guān)聯(lián)起來(lái)
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的應(yīng)用
// 提供構(gòu)造函數(shù)
fx.Provide(func() io.Reader {
return strings.NewReader("hello world")
}),
fx.Populate(&reader), // 通過(guò)依賴(lài)注入完成變量與具體類(lèi)的映射
)
app.Start(context.Background())
defer app.Stop(context.Background())
// 使用
// reader變量已與fx.Provide注入的實(shí)現(xiàn)類(lèi)關(guān)聯(lián)了
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參數(shù)
前面的使用方式一旦需要進(jìn)行注入的類(lèi)型過(guò)多,可以通過(guò)struct參數(shù)方式來(lái)解決
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
如果通過(guò)Provide提供構(gòu)造函數(shù)是生成相同類(lèi)型會(huì)有什么問(wèn)題?換句話(huà)也就是相同類(lèi)型擁有多個(gè)值呢?
下面兩種方式就是來(lái)解決這樣的問(wèn)題。
4、使用struct參數(shù)+Name標(biāo)簽
在Fx未使用Name或Group標(biāo)簽時(shí)不允許存在多個(gè)相同類(lèi)型的構(gòu)造函數(shù),一旦存在會(huì)觸發(fā)panic。
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
)
func main() {
type t3 struct {
Name string
}
//name標(biāo)簽的使用
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
上面通過(guò)Name標(biāo)簽即可完成在Fx容器注入相同類(lèi)型
5、使用struct參數(shù)+Group標(biāo)簽
使用group標(biāo)簽同樣也能完成上面的功能
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
)
func main() {
type t3 struct {
Name string
}
// 使用group標(biāo)簽
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簡(jiǎn)單應(yīng)用在上面的例子也做了簡(jiǎn)單講解
三、Fx定義
1、Annotated(位于annotated.go文件) 主要用于采用annotated的方式,提供Provide注入類(lèi)型
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兩個(gè)字段與前面提到的Name標(biāo)簽和Group標(biāo)簽是一樣的,只能選其一使用
2、App(位于app.go文件) 提供注入對(duì)象具體的容器、LiftCycle、容器的啟動(dòng)及停止、類(lèi)型變量及實(shí)現(xiàn)類(lèi)注入和兩者映射等操作
type App struct {
err error
container *dig.Container // 容器
lifecycle *lifecycleWrapper // 生命周期
provides []interface{} // 注入的類(lèi)型實(shí)現(xiàn)類(lèi)
invokes []interface{}
logger *fxlog.Logger
startTimeout time.Duration
stopTimeout time.Duration
errorHooks []ErrorHandler
donesMu sync.RWMutex
dones []chan os.Signal
}
// 新建一個(gè)App對(duì)象
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)
}
// 進(jìn)行app相關(guān)一些操作
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初始化過(guò)程是否正常
app.logger.Printf("Error after options were applied: %v", app.err)
return app
}
// 執(zhí)行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的源碼相對(duì)比較簡(jiǎn)單易懂在這里不在描述
具體源碼
3、Extract(位于extract.go文件)
主要用于在application啟動(dòng)初始化過(guò)程通過(guò)依賴(lài)注入的方式將容器中的變量值來(lái)填充給定的struct,其中target必須是指向struct的指針,并且只能填充可導(dǎo)出的字段(golang只能通過(guò)反射修改可導(dǎo)出并且可尋址的字段),Extract將被Populate代替。具體源碼
4、其他
諸如Populate是用來(lái)替換Extract的,而LiftCycle和inout.go涉及內(nèi)容比較多后續(xù)會(huì)單獨(dú)提供專(zhuān)屬文件說(shuō)明。
四、其他
在Fx中提供的構(gòu)造函數(shù)都是惰性調(diào)用,可以通過(guò)invocations在application啟動(dòng)來(lái)完成一些必要的初始化工作:fx.Invoke(function); 通過(guò)也可以按需自定義實(shí)現(xiàn)LiftCycle的Hook對(duì)應(yīng)的OnStart和OnStop用來(lái)完成手動(dòng)啟動(dòng)容器和關(guān)閉,來(lái)滿(mǎn)足一些自己實(shí)際的業(yè)務(wù)需求。
package main
import (
"context"
"log"
"net/http"
"os"
"time"
"go.uber.org/fx"
)
// Logger構(gòu)造函數(shù)
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
// http.Handler構(gòu)造函數(shù)
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構(gòu)造函數(shù)
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{ // 自定義生命周期過(guò)程對(duì)應(yīng)的啟動(dòng)和關(guān)閉的行為
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
}
// 注冊(cè)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), // 通過(guò)invoke來(lái)完成Logger、Handler、ServeMux的創(chuàng)建
)
startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil { // 手動(dòng)調(diào)用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 { // 手動(dòng)調(diào)用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?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者人面猴序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...沈念sama閱讀 145,261評(píng)論 1贊 308死咒序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...沈念sama閱讀 62,177評(píng)論 1贊 259救了他兩次的神仙讓他今天三更去死文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...開(kāi)封第一講書(shū)人閱讀 96,329評(píng)論 0贊 214道士緝兇錄:失蹤的賣(mài)姜人 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...開(kāi)封第一講書(shū)人閱讀 41,490評(píng)論 0贊 184?港島之戀(遺憾婚禮)正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...茶點(diǎn)故事閱讀 49,353評(píng)論 1贊 262惡毒庶女頂嫁案:這布局不是一般人想出來(lái)的文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...開(kāi)封第一講書(shū)人閱讀 39,028評(píng)論 1贊 179城市分裂傳說(shuō)那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...沈念sama閱讀 30,611評(píng)論 2贊 276雙鴛鴦連環(huán)套:你想象不到人心有多黑文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...開(kāi)封第一講書(shū)人閱讀 29,383評(píng)論 0贊 171萬(wàn)榮殺人案實(shí)錄序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...沈念sama閱讀 32,749評(píng)論 0贊 215?護(hù)林員之死正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...茶點(diǎn)故事閱讀 29,460評(píng)論 2贊 219?白月光啟示錄正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...茶點(diǎn)故事閱讀 30,814評(píng)論 1贊 232活死人序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...沈念sama閱讀 27,255評(píng)論 2贊 215?日本核電站爆炸內(nèi)幕正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...茶點(diǎn)故事閱讀 31,752評(píng)論 3贊 214男人毒藥:我在死后第九天來(lái)索命文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...開(kāi)封第一講書(shū)人閱讀 25,685評(píng)論 0贊 9一樁弒父案,背后竟有這般陰謀文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...開(kāi)封第一講書(shū)人閱讀 26,114評(píng)論 0贊 170情欲美人皮我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...沈念sama閱讀 33,747評(píng)論 2贊 234代替公主和親正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...茶點(diǎn)故事閱讀 33,901評(píng)論 2贊 238評(píng)論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 依賴(lài)注入系統(tǒng) - Fx 官方文檔
Go 依賴(lài)注入系統(tǒng) - Fx 官方文檔
1. 簡(jiǎn)介2. 入門(mén)教程2.1. 創(chuàng)建最小的應(yīng)用程序2.2. 添加 HTTP 服務(wù)2.3. 注冊(cè)處理器2.4. 添加 Logger2.5. 解耦注冊(cè)2.6. 注冊(cè)另一個(gè)處理器2.7. 注冊(cè)多個(gè)處理器3. 概念3.1. 應(yīng)用程序生命周期3.1.1. 生命周期鉤子3.2. 模塊3.2.1. 編寫(xiě)模塊3.2.1.1. 命名3.2.1.1.1. 包3.2.1.1.2. 參數(shù)和結(jié)果對(duì)象3.2.1.2. 導(dǎo)出邊界函數(shù)3.2.1.3. 使用參數(shù)對(duì)象3.2.1.4. 使用結(jié)果對(duì)象3.2.1.5. 不要提供你不擁有的東西3.2.1.6. 保持獨(dú)立模塊精簡(jiǎn)3.2.1.7. 謹(jǐn)慎地使用 Invoke4. 特性4.1. 參數(shù)對(duì)象4.1.1. 使用參數(shù)對(duì)象4.1.2. 添加新參數(shù)4.2. 結(jié)果對(duì)象4.2.1. 使用結(jié)果對(duì)象4.2.2. 添加新結(jié)果4.3. 注解(Annotation)4.3.1. 對(duì)函數(shù)進(jìn)行注解4.3.2. 將結(jié)構(gòu)體強(qiáng)制轉(zhuǎn)換為接口4.4. 值組(Value Group)4.4.1. 使用值組4.4.2. 依賴(lài)嚴(yán)格性4.4.2.1. 嚴(yán)格的值組4.4.2.2. 軟值組4.4.3. 向值組提供值4.4.3.1. 使用結(jié)果對(duì)象4.4.3.2. 使用帶注解的函數(shù)4.4.4. 從值組獲取值4.4.4.1. 使用參數(shù)對(duì)象4.4.4.2. 使用帶注解的函數(shù)Go 依賴(lài)注入系統(tǒng) - Fx 官方文檔官方文檔:https://uber-go.github.io/fx/get-started/1. 簡(jiǎn)介Fx 是用于 Go 的依賴(lài)注入系統(tǒng)。使用 Fx 可以:在設(shè)置應(yīng)用程序時(shí),減少樣板文件消除應(yīng)用程序中的全局狀態(tài)添加新組件,并且可以立即在整個(gè)應(yīng)用程序中進(jìn)行訪(fǎng)問(wèn)構(gòu)建通用的可共享模塊2. 入門(mén)教程本章將介紹 Fx 的基本用法。首先,做準(zhǔn)備工作。創(chuàng)建新項(xiàng)目mkdir fxdemocd fxdemogo mod init example.com/fxdemo安裝最新版本的 Fxgo get go.uber.org/[email protected]. 創(chuàng)建最小的應(yīng)用程序下面構(gòu)建 Fx 版的 hello-world,該應(yīng)用程序除打印一堆日志外,不做任何事情。編寫(xiě)最小化的 main.go:package main
import "go.uber.org/fx"
func main() { ?fx.New().Run()}運(yùn)行應(yīng)用程序:go run .將看到類(lèi)似下面的輸出:[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 應(yīng)用程序的默認(rèn)對(duì)象,但是它不做任何有意義的事情。使用 Ctrl-C 停止應(yīng)用程序。[Fx] RUNNING^C[Fx] INTERRUPT我們剛剛做了什么?通過(guò)不帶參數(shù)地調(diào)用 fx.New,構(gòu)建空 Fx 應(yīng)用程序。但是應(yīng)用程序通常會(huì)向 fx.New 傳遞參數(shù),以設(shè)置其組件。然后,使用 App.Run 方法運(yùn)行該應(yīng)用程序。該方法將阻塞到接收到停止信號(hào),在退出前,該方法將運(yùn)行必要的清理操作。Fx 主要用于常駐的服務(wù)端應(yīng)用程序;這些應(yīng)用程序在關(guān)閉時(shí)通常從部署系統(tǒng)接收信號(hào)。2.2. 添加 HTTP 服務(wù)下面向前一節(jié)的 Fx 應(yīng)用程序中添加 HTTP 服務(wù)。編寫(xiě)構(gòu)建 HTTP 服務(wù)的函數(shù)。// 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 如何啟動(dòng) HTTP 服務(wù)。這就是 fx.Lifecycle 參數(shù)的作用。使用 fx.Lifecycle 對(duì)象向應(yīng)用程序添加 lifecyle 鉤子。告訴 Fx 如何啟動(dòng)和停止 HTTP 服務(wù)。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 應(yīng)用程序。xxxxxxxxxxfunc main() { ?fx.New( ? ?fx.Provide(NewHTTPServer), ).Run()}運(yùn)行應(yīng)用程序。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”消息。因此服務(wù)未運(yùn)行。為解決該問(wèn)題,添加 fx.Invoke,請(qǐng)求被構(gòu)造的 Server。xxxxxxxxxx ?fx.New( ? ?fx.Provide(NewHTTPServer), ? ?fx.Invoke(func(*http.Server) {}), ).Run()再次運(yùn)行應(yīng)用程序。這次可以在輸出中看到“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向服務(wù)發(fā)送請(qǐng)求。xxxxxxxxxx$ curl http://localhost:8080404 page not found請(qǐng)求是 404,因?yàn)榉?wù)還不知道如何處理請(qǐng)求。下一節(jié)將修復(fù)該問(wèn)題。停止應(yīng)用程序。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 向應(yīng)用程序添加 HTTP Server。Server 連接到 Fx 應(yīng)用程序的生命周期 -- 當(dāng)調(diào)用 App.Run 時(shí),開(kāi)始服務(wù)請(qǐng)求,當(dāng)應(yīng)用程序收到停止信號(hào)時(shí),停止運(yùn)行。使用 fx.Invoke,保證始終實(shí)例化 HTTP Server,即便在應(yīng)用程序中沒(méi)有其它組件直接引用它。后面將介紹 Fx 生命周期是什么,以及如何使用。2.3. 注冊(cè)處理器前一節(jié)構(gòu)造的 Server 可以接收請(qǐng)求,但是該服務(wù)不知道如何處理請(qǐng)求。下面修復(fù)該問(wèn)題。定義基礎(chǔ)的 HTTP 處理器,該處理器將傳入的請(qǐng)求體拷貝到響應(yīng)。在文件底部添加如下代碼。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) }}將其提供給應(yīng)用程序:xxxxxxxxxx ? ?fx.Provide( ? ? ?NewHTTPServer, ? ? ?NewEchoHandler, ? ), ? ?fx.Invoke(func(*http.Server) {}),下面,編寫(xiě)構(gòu)造 *http.ServeMux 的函數(shù)。*http.ServeMux 將服務(wù)接收到的請(qǐng)求路由到不同的處理器。在本例中,它將發(fā)送到 /echo 的請(qǐng)求路由到 *EchoHandler,因此其構(gòu)造器接受 *EchoHandler 作為參數(shù)。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}同樣地,將其提供給應(yīng)用程序。xxxxxxxxxx ? ?fx.Provide( ? ? ?NewHTTPServer, ? ? ?NewServeMux, ? ? ?NewEchoHandler, ? ),注意,提供給 fx.Provide 的構(gòu)造器的順序無(wú)關(guān)緊要。最后,修改 NewHTTPServer 函數(shù),將 Server 連接到該 *ServeMux。xxxxxxxxxxfunc NewHTTPServer(lc fx.Lifecycle, mux *http.ServeMux) *http.Server { ?srv := &http.Server{Addr: ":8080", Handler: mux} ?lc.Append(fx.Hook{運(yùn)行 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 發(fā)送請(qǐng)求。xxxxxxxxxx$ curl -X POST -d 'hello' http://localhost:8080/echohello我們剛剛做了什么?使用 fx.Provide 添加更多組件。這些組件通過(guò)在構(gòu)造器中添加參數(shù)的方式,聲明彼此之間的依賴(lài)關(guān)系。Fx 將通過(guò)參數(shù)和函數(shù)的返回值,解析組件的依賴(lài)關(guān)系。2.4. 添加 Logger當(dāng)前的應(yīng)用程序?qū)ⅰ癝tarting HTTP server”消息打印到標(biāo)準(zhǔn)輸出,將錯(cuò)誤打印到標(biāo)準(zhǔn)錯(cuò)誤輸出。兩者都是全局狀態(tài)。我們應(yīng)該打印到 Logger 對(duì)象。本教程使用 Zap,但也可以使用任何日志記錄系統(tǒng)。將 Zap Logger 提供給應(yīng)用程序。本教程使用 zap.NewExample,但真實(shí)的應(yīng)用程序應(yīng)該使用 zap.NewProduction,或者構(gòu)建更加定制化的 Logger。xxxxxxxxxx ? ?fx.Provide( ? ? ?NewHTTPServer, ? ? ?NewServeMux, ? ? ?NewEchoHandler, ? ? ?zap.NewExample, ? ),在 EchoHandler 上添加持有 Logger 的字段,并且在 NewEchoHandler 中添加設(shè)置該字段的參數(shù)。xxxxxxxxxxtype EchoHandler struct { ?log *zap.Logger}
func NewEchoHandler(log *zap.Logger) *EchoHandler { ?return &EchoHandler{log: log}}在 EchoHandler.ServeHTTP 方法中,使用 Logger 代替向標(biāo)準(zhǔn)錯(cuò)誤輸出打印。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)) }}類(lèi)似地,更新 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] 消息。運(yùn)行應(yīng)用程序。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 請(qǐng)求。xxxxxxxxxx$ curl -X POST -d 'hello' http://localhost:8080/echohello我們剛剛做了什么?使用 fx.Provide 將另一個(gè)組件添加到應(yīng)用程序,并且將其注入進(jìn)需要打印日志的其它組件。為實(shí)現(xiàn)這一點(diǎn),只需要在構(gòu)造器中添加新參數(shù)。在可選步驟中,告訴 Fx 我們希望為 Fx 自身的操作提供自定義 Logger。使用現(xiàn)有的 fxevent.ZapLogger 從注入的 Logger 構(gòu)建該自定義 Logger,以使所有日志遵循相同的格式。2.5. 解耦注冊(cè)前面的 NewServeMux 明確聲明對(duì) EchoHandler 的依賴(lài)。這是不必要的緊耦合。ServeMux 是否真得需要知道確切的處理器實(shí)現(xiàn)?如果為 ServeMux 編寫(xiě)測(cè)試,那么不應(yīng)該構(gòu)造 EchoHandler。下面嘗試修復(fù)該問(wèn)題。在 main.go 中定義 Route 類(lèi)型。它是 http.Handler 的擴(kuò)展,該處理器知道其注冊(cè)路徑。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 實(shí)現(xiàn)該接口。xxxxxxxxxxfunc (*EchoHandler) Pattern() string { ?return "/echo"}在 main() 中,對(duì) NewEchoHandler 條目做注解,以聲明將該處理器當(dāng)作 Route 提供給應(yīng)用程序。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}運(yùn)行服務(wù)。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"}向其發(fā)送請(qǐng)求。xxxxxxxxxx$ curl -X POST -d 'hello' http://localhost:8080/echohello我們剛剛做了什么?引入接口從使用者解耦實(shí)現(xiàn)。然后使用 fx.Annotate 和 fx.As 對(duì)前面提供的構(gòu)造器做注解,以將其結(jié)果強(qiáng)制轉(zhuǎn)換為接口。通過(guò)這種方式,NewEchoHandler 可以繼續(xù)返回 *EchoHandler。2.6. 注冊(cè)另一個(gè)處理器下面添加另一個(gè)處理器。在同一文件中創(chuàng)建新處理器。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}}為該處理器實(shí)現(xiàn) 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 }}該處理器讀取其請(qǐng)求體,并且向調(diào)用者返回歡迎消息。將其作為 Route 提供給應(yīng)用程序,放在 NewEchoHandler 旁邊。xxxxxxxxxx ? ? ?fx.Annotate( ? ? ? ?NewEchoHandler, ? ? ? ?fx.As(new(Route)), ? ? ), ? ? ?fx.Annotate( ? ? ? ?NewHelloHandler, ? ? ? ?fx.As(new(Route)), ? ? ),運(yùn)行應(yīng)用程序 - 服務(wù)將啟動(dòng)失敗。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)輸出很多,但在錯(cuò)誤信息內(nèi)部,可以看到:xxxxxxxxxxcannot provide main.Route from [0].Field0: already provided by "main".NewEchoHandler ([..]/main.go:80)失敗原因是 Fx 不允許容器中存在相同類(lèi)型的兩個(gè)實(shí)例,并且未對(duì)它們進(jìn)行注解。NewServeMux 不知道使用哪個(gè) Route。下面進(jìn)行修復(fù)。在 main() 中,使用名稱(chēng)對(duì) NewEchoHandler 和 NewHelloHandler 進(jìn)行注解。xxxxxxxxxx ? ? ?fx.Annotate( ? ? ? ?NewEchoHandler, ? ? ? ?fx.As(new(Route)), ? ? ? ?fx.ResultTags(`name:"echo"`), ? ? ), ? ? ?fx.Annotate( ? ? ? ?NewHelloHandler, ? ? ? ?fx.As(new(Route)), ? ? ? ?fx.ResultTags(`name:"hello"`), ? ? ),向 NewServeMux 添加另一個(gè) Route 參數(shù)。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() 中,對(duì) NewServeMux 進(jìn)行注解,以使用這兩個(gè)名稱(chēng)值。xxxxxxxxxx ? ?fx.Provide( ? ? ?NewHTTPServer, ? ? ?fx.Annotate( ? ? ? ?NewServeMux, ? ? ? ?fx.ParamTags(`name:"echo"`, `name:"hello"`), ? ? ),運(yùn)行程序。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"}發(fā)送請(qǐng)求。xxxxxxxxxx$ curl -X POST -d 'hello' http://localhost:8080/echohello
$ curl -X POST -d 'gopher' http://localhost:8080/helloHello, gopher我們剛剛做了什么?添加構(gòu)造器,用于生成與現(xiàn)有類(lèi)型相同類(lèi)型的值。使用 fx.ResultTags 對(duì)構(gòu)造器進(jìn)行注解,生成命名值,使用 fx.ParamTags 對(duì)使用者進(jìn)行注解,使用這些命名值。2.7. 注冊(cè)多個(gè)處理器前面的示例有兩個(gè)處理器,但是在構(gòu)造 NewServeMux 時(shí),通過(guò)名稱(chēng)顯式地引用它們。如果添加更多處理器,這種方式將變得不方便。如果 NewServeMux 無(wú)需知道有多少個(gè)處理器或者名稱(chēng),而只接受待注冊(cè)的處理器列表,那么更可取。修改 NewServeMux,使其操作 Route 對(duì)象的列表。xxxxxxxxxxfunc NewServeMux(routes []Route) *http.ServeMux { ?mux := http.NewServeMux() ?for _, route := range routes { ? ?mux.Handle(route.Pattern(), route) } ?return mux}在 main 中,對(duì) NewServeMux 條目進(jìn)行注解,以說(shuō)明它接收包含“routes”組內(nèi)容的切片。xxxxxxxxxx ? ?fx.Provide( ? ? ?NewHTTPServer, ? ? ?fx.Annotate( ? ? ? ?NewServeMux, ? ? ? ?fx.ParamTags(`group:"routes"`), ? ? ),定義新函數(shù) AsRoute,以構(gòu)造提供給該組的函數(shù)。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 構(gòu)造器,以便它們將路由提供給該組。xxxxxxxxxx ? ?fx.Provide( ? ? ?AsRoute(NewEchoHandler), ? ? ?AsRoute(NewHelloHandler), ? ? ?zap.NewExample, ? ),最后,運(yùn)行該應(yīng)用程序。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"}發(fā)送請(qǐng)求。xxxxxxxxxx$ curl -X POST -d 'hello' http://localhost:8080/echohello
$ curl -X POST -d 'gopher' http://localhost:8080/helloHello, gopher我們剛剛做了什么?對(duì) NewServeMux 進(jìn)行注解,使其將值組當(dāng)作切片使用,并且對(duì)現(xiàn)有處理器構(gòu)造器進(jìn)行注解,將其提供給該值組。應(yīng)用程序中的任何構(gòu)造函器只要結(jié)果符合 Route 接口,都可以向該值組提供值。它們將被收集在一起,并且被傳遞給 ServeMux 構(gòu)造器。3. 概念3.1. 應(yīng)用程序生命周期Fx 應(yīng)用程序的生命周期有兩個(gè)階段:初始化和執(zhí)行。這兩個(gè)階段依次由多個(gè)步驟組成。在初始化期間,F(xiàn)x 將:注冊(cè)傳遞給 fx.Provide 的所有構(gòu)造器注冊(cè)傳遞給 fx.Decorate 的所有裝飾器運(yùn)行傳遞給 fx.Invoke 的所有函數(shù),按需調(diào)用構(gòu)造器和裝飾器在執(zhí)行期間,F(xiàn)x 將:運(yùn)行由 Provider、Decorator、Invoked 函數(shù)追加到應(yīng)用程序的所有啟動(dòng)鉤子等待停止運(yùn)行的信號(hào)運(yùn)行追加到應(yīng)用程序的所有關(guān)閉鉤子3.1.1. 生命周期鉤子當(dāng)應(yīng)用程序啟動(dòng)或關(guān)閉時(shí),生命周期鉤子提供調(diào)度 Fx 執(zhí)行的工作的能力。Fx 提供兩種鉤子:?jiǎn)?dòng)鉤子,也被稱(chēng)為 OnStart 鉤子。Fx 按照追加的順序運(yùn)行這些鉤子。關(guān)閉鉤子,也被稱(chēng)為 OnStop 鉤子。運(yùn)行順序與追加順序相反通常,提供啟動(dòng)鉤子的組件也提供相應(yīng)的關(guān)閉鉤子,以釋放啟動(dòng)時(shí)申請(qǐng)的資源。Fx 使用硬超時(shí)強(qiáng)制運(yùn)行這兩種鉤子。因此,鉤子只有在需要調(diào)度工作時(shí)才可以阻塞。換言之,鉤子不能因同步運(yùn)行長(zhǎng)時(shí)間運(yùn)行的任務(wù)而阻塞鉤子應(yīng)該在后臺(tái)協(xié)程中調(diào)度長(zhǎng)時(shí)間運(yùn)行的任務(wù)關(guān)閉鉤子應(yīng)該關(guān)閉啟動(dòng)鉤子啟動(dòng)的后臺(tái)工作3.2. 模塊Fx 模塊是可共享的 Go 庫(kù)或包,向 Fx 應(yīng)用程序提供自包含(Self-Contained)的功能。3.2.1. 編寫(xiě)模塊為編寫(xiě) Fx 模塊:定義頂級(jí)的 Module 變量(通過(guò) fx.Module 調(diào)用構(gòu)建)。給模塊起一個(gè)簡(jiǎn)短的、容易記住的日志名稱(chēng)。xxxxxxxxxxvar Module = fx.Module("server",使用 fx.Provide 添加模塊的組件。xxxxxxxxxxvar Module = fx.Module("server", ?fx.Provide( ? ?New, ? ?parseConfig, ),)如果模塊擁有必須運(yùn)行的函數(shù),那么為其添加 fx.Invoke。xxxxxxxxxxvar Module = fx.Module("server", ?fx.Provide( ? ?New, ? ?parseConfig, ), ?fx.Invoke(startServer),)如果模塊在使用其依賴(lài)之前需要裝飾它們,那么為其添加 fx.Decorate 調(diào)用。xxxxxxxxxxvar Module = fx.Module("server", ?fx.Provide( ? ?New, ? ?parseConfig, ), ?fx.Invoke(startServer), ?fx.Decorate(wrapLogger),)最后,如果希望將構(gòu)造器的輸出保留在模塊(以及模塊包含的模塊)中,那么在提供時(shí)添加 fx.Private。xxxxxxxxxxvar Module = fx.Module("server", ?fx.Provide( ? ?New, ), ?fx.Provide( ? ?fx.Private, ? ?parseConfig, ), ?fx.Invoke(startServer), ?fx.Decorate(wrapLogger),)在這種情況下,parseConfig 是“server”模塊私有的。任何包含“server”的模塊都不能使用結(jié)果 Config 類(lèi)型,因?yàn)樗鼉H對(duì)“server”模塊可見(jiàn)。以上是關(guān)于編寫(xiě)模塊的全部?jī)?nèi)容。本節(jié)的其余部分涵蓋 Uber 為編寫(xiě) Fx 模塊建立的標(biāo)準(zhǔn)和慣例。3.2.1.1. 命名3.2.1.1.1. 包獨(dú)立的 Fx 模塊,即那些作為獨(dú)立庫(kù)分發(fā)的模塊,或者那些在庫(kù)中有獨(dú)立 Go 包的模塊,應(yīng)該以它們包裝的庫(kù)或者提供的功能命名,并且添加“fx”后綴。BadGoodpackage mylibpackage mylibfxpackage httputilpackage httputilfx如果 Fx 模塊是另一個(gè) Go 包的一部分,或者是為特定應(yīng)用程序編寫(xiě)的單服務(wù)模塊,那么可以省略該后綴。3.2.1.1.2. 參數(shù)和結(jié)果對(duì)象參數(shù)和結(jié)果對(duì)象類(lèi)型應(yīng)該以它們對(duì)應(yīng)的函數(shù)命名,命名方式是在函數(shù)名后添加 Params 或 Result 后綴。例外:如果函數(shù)名以 New 開(kāi)頭,那么在添加 Params 或 Result 后綴前,去掉 New 前綴。函數(shù)參數(shù)對(duì)象結(jié)果對(duì)象NewParamsResultRunRunParamsRunResultNewFooFooParamsFooResult3.2.1.2. 導(dǎo)出邊界函數(shù)如果功能無(wú)法通過(guò)其它方式訪(fǎng)問(wèn),那么導(dǎo)出模塊通過(guò) fx.Provide 或 fx.Invoke 使用的函數(shù)。xxxxxxxxxxvar Module = fx.Module("server", ?fx.Provide( ? ?New, ? ?parseConfig, ),)
type Config struct { ?Addr string `yaml:"addr"`}
func New(p Params) (Result, error) {在本例中,不導(dǎo)出 parseConfig,因?yàn)樗皇呛?jiǎn)單的 yaml.Decode,無(wú)需暴露,但仍然導(dǎo)出 Config,以便用戶(hù)可以自己解碼?;驹瓌t:應(yīng)該可以在不使用 Fx 的情況下,使用 Fx 模塊。用戶(hù)應(yīng)該可以直接調(diào)用構(gòu)造器,獲取與使用 Fx 時(shí)模塊提供的相同功能。這對(duì)于緊急情況和部分遷移是必要的。Bad case:不使用 Fx,無(wú)法構(gòu)建 serverxxxxxxxxxxvar Module = fx.Module("server",fx.Provide(newServer),)
func newServer(...) (*Server, error)3.2.1.3. 使用參數(shù)對(duì)象由模塊暴露的函數(shù)不應(yīng)該直接接受依賴(lài)項(xiàng)作為參數(shù)。而應(yīng)該使用參數(shù)對(duì)象。xxxxxxxxxxtype Params struct { ?fx.In
?Log ? ?*zap.Logger ?Config Config}
func New(p Params) (Result, error) {基本原則:模塊不可避免地需要聲明新依賴(lài)。通過(guò)使用參數(shù)對(duì)象,可以以向后兼容的方式添加新的可選依賴(lài),并且無(wú)需更改函數(shù)簽名。Bad case:無(wú)法在不破壞的情況下,添加新參數(shù)xxxxxxxxxxfunc New(log *zap.Logger) (Result, error)3.2.1.4. 使用結(jié)果對(duì)象由模塊暴露的函數(shù)不應(yīng)該將其結(jié)果聲明為常規(guī)返回值。而應(yīng)該使用結(jié)果對(duì)象。xxxxxxxxxxtype Result struct { ?fx.Out
?Server *Server}
func New(p Params) (Result, error) {基本原則:模塊將不可避免地需要返回新結(jié)果。通過(guò)使用結(jié)果對(duì)象,可以以向后兼容的方式生成新結(jié)果,并且無(wú)需更改函數(shù)簽名。Bad case:無(wú)法在不破壞的情況下,添加新結(jié)果xxxxxxxxxxfunc New(Params) (*Server, error)3.2.1.5. 不要提供你不擁有的東西Fx 模塊應(yīng)該只向應(yīng)用程序提供在其權(quán)限范圍內(nèi)的類(lèi)型。模塊不應(yīng)該向應(yīng)用程序提供它們碰巧使用的值。模塊也不應(yīng)該大量捆綁其它模塊。基本原則:這讓使用者可以自由地選擇依賴(lài)的來(lái)源和方式。他們可以使用你推薦的方法(比如,“include zapfix.module”),或者構(gòu)建該依賴(lài)的變體。Bad case:提供依賴(lài)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)例外:組織或團(tuán)隊(duì)級(jí)別的“kitchen sink”模塊專(zhuān)門(mén)用于捆綁其它模塊,可以忽略該規(guī)則。比如,Uber 的 uberfx.Module 模塊捆綁若干其它獨(dú)立模塊。該模塊中的所有東西都被所有服務(wù)所需要。3.2.1.6. 保持獨(dú)立模塊精簡(jiǎn)獨(dú)立的 Fx 模塊 -- 名稱(chēng)以“fx”結(jié)尾的模塊很少包含重要的業(yè)務(wù)邏輯。如果 Fx 模塊位于包含重要業(yè)務(wù)邏輯的包中,那么其名稱(chēng)中不應(yīng)該有“fx”后綴?;驹瓌t:在不重寫(xiě)業(yè)務(wù)邏輯的情況下,使用方應(yīng)該可以遷移到或者離開(kāi) Fx。Good case:業(yè)務(wù)邏輯使用 net/http.Clientxxxxxxxxxxpackage httpfx
import "net/http"
type Result struct { fx.Out
Client *http.Client}Bad case:Fx 模塊實(shí)現(xiàn) Loggerxxxxxxxxxxpackage logfx
type Logger struct {// ...}
func New(...) Logger3.2.1.7. 謹(jǐn)慎地使用 Invoke當(dāng)選擇在模塊中使用 fx.Invoke 時(shí)要謹(jǐn)慎。根據(jù)設(shè)計(jì),F(xiàn)x 僅在應(yīng)用程序通過(guò)另一個(gè)模塊、構(gòu)造器或 Invoke 直接或間接地使用構(gòu)造器的結(jié)果時(shí),才執(zhí)行通過(guò) fx.Provide 添加的構(gòu)造器。另一方面,F(xiàn)x 無(wú)條件地運(yùn)行使用 fx.Invoke 添加的函數(shù),并且在這樣做時(shí),實(shí)例化其依賴(lài)的每個(gè)直接值和傳遞值。4. 特性4.1. 參數(shù)對(duì)象參數(shù)對(duì)象是僅用于攜帶特定函數(shù)或方法的參數(shù)的對(duì)象。通常專(zhuān)門(mén)為函數(shù)定義參數(shù)對(duì)象,并且不與其它函數(shù)共享。參數(shù)對(duì)象不是像“user”那樣的通用對(duì)象,而是專(zhuān)門(mén)構(gòu)建的對(duì)象,比如“GetUser 函數(shù)的參數(shù)”。在 Fx 中,參數(shù)對(duì)象只包含導(dǎo)出字段,并且始終附帶 fx.In 標(biāo)簽。4.1.1. 使用參數(shù)對(duì)象為在 Fx 中使用參數(shù)對(duì)象,執(zhí)行以下步驟:定義新結(jié)構(gòu)體類(lèi)型,命名為構(gòu)造器名稱(chēng)加上 Params 后綴。如果構(gòu)造器名稱(chēng)為 NewClient,那么將結(jié)構(gòu)體命名為 ClientParams。如果構(gòu)造器名稱(chēng)為 New,那么將結(jié)構(gòu)體命名為 Params。該命名不是必須的,但是是很好的約定。xxxxxxxxxxtype ClientParams struct {}將 fx.In 嵌進(jìn)結(jié)構(gòu)體。xxxxxxxxxxtype ClientParams struct { ?fx.In按值將該新類(lèi)型當(dāng)作參數(shù)添加到構(gòu)造器中。xxxxxxxxxxfunc NewClient(p ClientParams) (*Client, error) {將構(gòu)造器的依賴(lài)項(xiàng)添加為該結(jié)構(gòu)體的導(dǎo)出字段。xxxxxxxxxxtype ClientParams struct { ?fx.In
?Config ? ? ClientConfig ?HTTPClient *http.Client}在構(gòu)造器中使用這些字段。xxxxxxxxxxfunc NewClient(p ClientParams) (*Client, error) { ?return &Client{ ? ?url: ?p.Config.URL, ? ?http: p.HTTPClient, ? ?// ... }, nil4.1.2. 添加新參數(shù)可以通過(guò)向參數(shù)對(duì)象添加新字段的方式,為構(gòu)造器添加新參數(shù)。為向后兼容,新字段必須是可選的。接受現(xiàn)有的參數(shù)對(duì)象。xxxxxxxxxxtype Params struct { ?fx.In
?Config ? ? ClientConfig ?HTTPClient *http.Client}
func New(p Params) (*Client, error) {為新依賴(lài),向參數(shù)對(duì)象中添加新字段,并且將其標(biāo)記為可選的,以保證此改動(dòng)向后兼容。xxxxxxxxxxtype Params struct { ?fx.In
?Config ? ? ClientConfig ?HTTPClient *http.Client ?Logger *zap.Logger `optional:"true"`}在構(gòu)造器中,使用該字段。確保處理缺少該字段的情況 - 在該情況下,將取其類(lèi)型的零值。xxxxxxxxxxfunc New(p Params) (*Client, error) { ?log := p.Logger ?if log == nil { ? ?log = zap.NewNop() } ?// ...4.2. 結(jié)果對(duì)象結(jié)果對(duì)象是僅用于攜帶特定函數(shù)或方法的結(jié)果的對(duì)象。與參數(shù)對(duì)象一樣,專(zhuān)門(mén)為單個(gè)函數(shù)定義結(jié)果對(duì)象,并且不與其它函數(shù)共享。在 Fx 中,結(jié)果對(duì)象只包含導(dǎo)出字段,并且始終附帶 fx.Out 標(biāo)簽。4.2.1. 使用結(jié)果對(duì)象為在 Fx 中使用結(jié)果對(duì)象,執(zhí)行以下步驟:定義新結(jié)構(gòu)體類(lèi)型,命名為構(gòu)造器名稱(chēng)加上 Result 后綴。如果構(gòu)造器名稱(chēng)為 NewClient,那么將結(jié)構(gòu)體命名為 ClientResult。如果構(gòu)造器名稱(chēng)為 New,那么將結(jié)構(gòu)體命名為 Result。該命名不是必須的,但是是很好的約定。xxxxxxxxxxtype ClientResult struct {}將 fx.Out 嵌進(jìn)結(jié)構(gòu)體。xxxxxxxxxxtype ClientResult struct { ?fx.Out按值將該新類(lèi)型用作構(gòu)造器的返回值。xxxxxxxxxxfunc NewClient() (ClientResult, error) {將構(gòu)造器生成的值添加為該結(jié)構(gòu)體上的導(dǎo)出字段。xxxxxxxxxxtype ClientResult struct { ?fx.Out
?Client *Client}在構(gòu)造器中,設(shè)置這些字段,并且返回該結(jié)構(gòu)體的實(shí)例。xxxxxxxxxxfunc NewClient() (ClientResult, error) { ?client := &Client{ ? ?// ... } ?return ClientResult{Client: client}, nil}4.2.2. 添加新結(jié)果可以以完全向后兼容的方式,向現(xiàn)有結(jié)果對(duì)象添加新值。接受現(xiàn)有結(jié)果對(duì)象。xxxxxxxxxxtype Result struct { ?fx.Out
?Client *Client}
func New() (Result, error) { ?client := &Client{ ? ?// ... } ?return Result{ ? ?Client: client, }, nil為新結(jié)果,向結(jié)果對(duì)象中添加新字段。xxxxxxxxxxtype Result struct { ?fx.Out
?Client *Client ?Inspector *Inspector}在構(gòu)造器中,設(shè)置該字段。xxxxxxxxxx ?return Result{ ? ?Client: client, ? ?Inspector: &Inspector{ ? ? ?// ... ? }, }, nil4.3. 注解(Annotation)可以在將函數(shù)和值傳遞給 fx.Provide、fx.Supply、fx.Invoke、fx.Decorate 或 fx.Replace 之前,使用 fx.Annotate 函數(shù)對(duì)其進(jìn)行注解。4.3.1. 對(duì)函數(shù)進(jìn)行注解先決條件函數(shù)需要滿(mǎn)足:當(dāng)使用 fx.ParamTags 進(jìn)行注解時(shí),不接受參數(shù)對(duì)象當(dāng)使用 fx.ResultTags 進(jìn)行注解時(shí),不返回結(jié)果對(duì)象步驟給定傳遞給 fx.Provide、fx.Invoke 或 fx.Decorate 的函數(shù)。xxxxxxxxxx ? ?fx.Provide( ? ? ?NewHTTPClient, ? ),使用 fx.Annotate 包裝函數(shù)。xxxxxxxxxx ? ?fx.Provide( ? ? ?fx.Annotate( ? ? ? ?NewHTTPClient, ? ? ), ? ),在 fx.Annotate 內(nèi)部,傳入注解。xxxxxxxxxx ? ?fx.Provide( ? ? ?fx.Annotate( ? ? ? ?NewHTTPClient, ? ? ? ?fx.ResultTags(`name:"client"`), ? ? ), ? ),該注解使用名稱(chēng)標(biāo)記函數(shù)的結(jié)果。4.3.2. 將結(jié)構(gòu)體強(qiáng)制轉(zhuǎn)換為接口可以使用函數(shù)注解將函數(shù)返回的結(jié)構(gòu)體值強(qiáng)制轉(zhuǎn)換為其它函數(shù)使用的接口。先決條件生成結(jié)構(gòu)體或指針值的函數(shù)。xxxxxxxxxxfunc NewHTTPClient(Config) (*http.Client, error) {使用生產(chǎn)者結(jié)果的函數(shù)。xxxxxxxxxxfunc NewGitHubClient(client *http.Client) *github.Client {將這兩個(gè)函數(shù)提供給 Fx 應(yīng)用程序。xxxxxxxxxx ? ?fx.Provide( ? ? ?NewHTTPClient, ? ? ?NewGitHubClient, ? ),步驟聲明匹配 *http.Client 的 API 的接口。xxxxxxxxxxtype HTTPClient interface { ?Do(*http.Request) (*http.Response, error)}
// 下面的語(yǔ)句在編譯時(shí)檢查接口是否匹配 http.Client 的 API。var _ HTTPClient = (*http.Client)(nil)修改使用者,使其接受接口,而非結(jié)構(gòu)體。xxxxxxxxxxfunc NewGitHubClient(client HTTPClient) *github.Client {最后,使用 fx.As 對(duì)生產(chǎn)者進(jìn)行注解,說(shuō)明它生成接口值。xxxxxxxxxx ? ?fx.Provide( ? ? ?fx.Annotate( ? ? ? ?NewHTTPClient, ? ? ? ?fx.As(new(HTTPClient)), ? ? ), ? ? ?NewGitHubClient, ? ),使用該改動(dòng):被注解的函數(shù)僅將接口放進(jìn)容器生產(chǎn)者的 API 保持不變使用者與實(shí)現(xiàn)解耦,可以獨(dú)立測(cè)試4.4. 值組(Value Group)值組是相同類(lèi)型的值的集合。Fx 應(yīng)用程序中的任何構(gòu)造器都可以向值組提供值。類(lèi)似地,任何使用者都可以在不知道生產(chǎn)者完整列表的情況下從值組中讀取值。提示Fx 生成的值以隨機(jī)順序提供給值組。不要對(duì)值組順序做任何假設(shè)。4.4.1. 使用值組4.4.2. 依賴(lài)嚴(yán)格性通過(guò)值組形成的依賴(lài)關(guān)系可以是:嚴(yán)格的:始終被使用軟的:僅當(dāng)在其它地方請(qǐng)求相應(yīng)的構(gòu)造器時(shí),才被使用默認(rèn)情況下,值組的賴(lài)是嚴(yán)格的。4.4.2.1. 嚴(yán)格的值組無(wú)論生產(chǎn)者是否被應(yīng)用程序使用,值組都使用嚴(yán)格的值組依賴(lài)。假設(shè)構(gòu)造器 NewFoo 生成兩個(gè)值:A 和 B。將值 A 提供給值組 []A,函數(shù) Run 使用該值組,應(yīng)用程序使用 fx.Invoke 調(diào)用函數(shù) Run。對(duì)于嚴(yán)格的值組,F(xiàn)x 將運(yùn)行 NewFoo,填充 []A 組,而不管應(yīng)用程序是否直接或間接地使用其它結(jié)果(B)。4.4.2.2. 軟值組僅當(dāng)生成軟值組依賴(lài)的構(gòu)造器被 Fx 調(diào)用時(shí),值組才使用軟值組依賴(lài) -- 因?yàn)閼?yīng)用程序直接或間接地使用它們的其它結(jié)果。下面的組織結(jié)構(gòu)除值組是軟的外,與前一節(jié)類(lèi)似。對(duì)于軟值組,F(xiàn)x 僅在 A 或 B 被應(yīng)用程序中的另一個(gè)組件直接或間接地使用時(shí),才運(yùn)行 NewFoo 填充 []A 組。4.4.3. 向值組提供值為向類(lèi)型為 T 的值組提供值,必須使用 group:"$name" 標(biāo)記 T 結(jié)果,其中 $name 是值組名稱(chēng)??梢酝ㄟ^(guò)如下方式實(shí)現(xiàn):使用結(jié)果對(duì)象使用帶注解的函數(shù)4.4.3.1. 使用結(jié)果對(duì)象可以使用結(jié)果對(duì)象標(biāo)記函數(shù)的結(jié)果,并且將其提供給值組。先決條件生成結(jié)果對(duì)象的函數(shù)。xxxxxxxxxxtype Result struct { ?fx.Out
?// ...}
func New( /* ... */ ) (Result, error) { ?// ... ?return Result{ ? ?// ... ? ?Watcher: watcher, }, nil}將函數(shù)提供給 Fx 應(yīng)用程序。xxxxxxxxxx ?fx.Provide(New),步驟使用想要生成的值的類(lèi)型向結(jié)果對(duì)象添加新導(dǎo)出字段,并且用值組的名稱(chēng)標(biāo)記該字段。xxxxxxxxxxtype Result struct { ?fx.Out
?// ... ?Watcher Watcher `group:"watchers"`}在函數(shù)中,將該字段設(shè)置為想要提供給值組的值。xxxxxxxxxxfunc New( /* ... */ ) (Result, error) { ?// ... ?watcher := &watcher{ ? ?// ... }
?return Result{ ? ?// ... ? ?Watcher: watcher, }, nil}4.4.3.2. 使用帶注解的函數(shù)可以使用注解向值組發(fā)送函數(shù)的結(jié)果。先決條件生成值組所需類(lèi)型的值的函數(shù)。xxxxxxxxxxfunc NewWatcher( /* ... */ ) (Watcher, error) { ?// ...將函數(shù)提供給 Fx 應(yīng)用程序。xxxxxxxxxx ?fx.Provide( ? ?NewWatcher, ),步驟使用 fx.Annotate 包裝傳遞給 fx.Provide 的函數(shù)。xxxxxxxxxx ?fx.Provide( ? ?fx.Annotate( ? ? ?NewWatcher, ? ), ),對(duì)該函數(shù)進(jìn)行注解,以說(shuō)明將其結(jié)果提供給值組。xxxxxxxxxx ? ?fx.Annotate( ? ? ?NewWatcher, ? ? ?fx.ResultTags(`group:"watchers"`), ? )提示:類(lèi)型無(wú)需必須匹配如果注解的函數(shù)不生成與組相同的類(lèi)型,那么可以將其強(qiáng)制轉(zhuǎn)換為該類(lèi)型:xxxxxxxxxxfunc NewFileWatcher( /* ... */ ) (*FileWatcher, error) {仍然可以使用注解將其提供給值組。xxxxxxxxxx fx.Annotate( ? NewFileWatcher, ? fx.As(new(Watcher)), ? fx.ResultTags(`group:"watchers"`), ),4.4.4. 從值組獲取值為使用類(lèi)型為 T 的值組,必須使用 group:"$name" 標(biāo)記 []T 依賴(lài),其中 $name 是值組的名稱(chēng)??梢酝ㄟ^(guò)如下方式實(shí)現(xiàn):使用參數(shù)對(duì)象使用帶注解的函數(shù)4.4.4.1. 使用參數(shù)對(duì)象可以使用參數(shù)對(duì)象將函數(shù)的切片參數(shù)標(biāo)記為值組。先決條件使用參數(shù)對(duì)象的函數(shù)。xxxxxxxxxxtype Params struct { ?fx.In
?// ...}
func New(p Params) (Result, error) { ?// ...將函數(shù)提供給 Fx 應(yīng)用程序。xxxxxxxxxx ?fx.Provide(New),步驟向參數(shù)對(duì)象添加類(lèi)型為 []T 的新導(dǎo)出字段,其中 T 是值組中值的種類(lèi)。使用值組名稱(chēng)標(biāo)記該字段。xxxxxxxxxxtype Params struct { ?fx.In
?// ... ?Watchers []Watcher `group:"watchers"`}在接收該參數(shù)對(duì)象的函數(shù)中使用該切片。xxxxxxxxxxfunc New(p Params) (Result, error) { ?// ... ?for _, w := range p.Watchers { ? ?// ...警告不要依賴(lài)切片里的值的順序。因?yàn)轫樞蚴请S機(jī)的。4.4.4.2. 使用帶注解的函數(shù)可以使用注解從函數(shù)中使用值組。先決條件接受組中的值類(lèi)型的切片的函數(shù)。xxxxxxxxxxfunc NewEmitter(watchers []Watcher) (*Emitter, error) {將函數(shù)提供給 Fx 應(yīng)用程序。xxxxxxxxxx ?fx.Provide( ? ?NewEmitter, ),步驟使用 fx.Annotate 包裝傳遞給 fx.Provide 的函數(shù)。xxxxxxxxxx ?fx.Provide( ? ?fx.Annotate( ? ? ?NewEmitter, ? ), ),對(duì)該函數(shù)進(jìn)行注解,說(shuō)明其切片參數(shù)為值組。xxxxxxxxxx ? ?fx.Annotate( ? ? ?NewEmitter, ? ? ?fx.ParamTags(`group:"watchers"`), ? ),在函數(shù)中使用該切片。xxxxxxxxxxfunc NewEmitter(watchers []Watcher) (*Emitter, error) { ?for _, w := range watchers { ? ?// ...提示:函數(shù)可以接受變長(zhǎng)參數(shù)可以在接受變長(zhǎng)參數(shù)的函數(shù)中使用值組。xxxxxxxxxxfunc EmitterFrom(watchers ...Watcher) (*Emitter, error) {return &Emitter{ws: watchers}, nil}對(duì)變長(zhǎng)參數(shù)進(jìn)行注解。xxxxxxxxxx fx.Annotate( ? EmitterFrom, ? fx.ParamTags(`group:"watchers"`), ),?
fx package - go.uber.org/fx - Go Packages
fx package - go.uber.org/fx - Go Packages
Skip to Main Content
Why Go
Case Studies
Common problems companies solve with Go
Use Cases
Stories about how and why companies use Go
Security Policy
How Go can help keep you secure by default
Learn
Docs
Effective Go
Tips for writing clear, performant, and idiomatic Go code
Go User Manual
A complete introduction to building software with Go
Standard library
Reference documentation for Go's standard library
Release Notes
Learn what's new in each Go release
Packages
Community
Recorded Talks
Videos from prior events
Meetups
Meet other local Go developers
Conferences
Learn and network with Go developers from around the world
Go blog
The Go project's official blog.
Go project
Get help and stay informed from Go
Get connected
Why Go
Why Go
Case Studies
Use Cases
Security Policy
Learn
Docs
Docs
Effective Go
Go User Manual
Standard library
Release Notes
Packages
Community
Community
Recorded Talks
Meetups
Conferences
Go blog
Go project
Get connected
Discover Packages
go.uber.org/fx
fx
package
module
Version:
v1.20.1
Opens a new window with list of versions in this module.
Latest
Latest
This package is not in the latest version of its module.
Go to latest
Published: Oct 17, 2023
License: MIT
Opens a new window with license information.
Imports: 21
Opens a new window with list of imports.
Imported by: 4,263
Opens a new window with list of known importers.
Main
Versions
Licenses
Imports
Imported By
Details
Valid go.mod file
The Go module system was introduced in Go 1.11 and is the official dependency management
solution for Go.
Redistributable license
Redistributable licenses place minimal restrictions on how software can be used,
modified, and redistributed.
Tagged version
Modules with tagged versions give importers more predictable builds.
Stable version
When a project reaches major version v1 it is considered stable.
Learn more about best practices
Repository
github.com/uber-go/fx
Links
Open Source Insights
Jump to ...
README
Installation
Getting started
Stability
Stargazers over time
Documentation
Overview
Index
Examples
Constants
Variables
Functions
Annotate(t, anns)
ValidateApp(opts)
VisualizeError(err)
Types
type Annotated
(a) String()
type Annotation
As(interfaces)
From(interfaces)
OnStart(onStart)
OnStop(onStop)
ParamTags(tags)
ResultTags(tags)
type App
New(opts)
(app) Done()
(app) Err()
(app) Run()
(app) Start(ctx)
(app) StartTimeout()
(app) Stop(ctx)
(app) StopTimeout()
(app) Wait()
type DotGraph
type ErrorHandler
type Hook
StartHook(start)
StartStopHook(start, stop)
StopHook(stop)
type HookFunc
type In
type Lifecycle
type Option
Decorate(decorators)
Error(errs)
ErrorHook(funcs)
Extract(target)
Invoke(funcs)
Logger(p)
Module(name, opts)
Options(opts)
Populate(targets)
Provide(constructors)
RecoverFromPanics()
Replace(values)
StartTimeout(v)
StopTimeout(v)
Supply(values)
WithLogger(constructor)
type Out
type Printer
type ShutdownOption
ExitCode(code)
ShutdownTimeout(timeout)
type ShutdownSignal
(sig) String()
type Shutdowner
Source Files
Directories
README
README
?
Fx
Fx is a dependency injection system for Go.
Benefits
Eliminate globals: Fx helps you remove global state from your application.
No more init() or global variables. Use Fx-managed singletons.
Code reuse: Fx lets teams within your organization build loosely-coupled
and well-integrated shareable components.
Battle tested: Fx is the backbone of nearly all Go services at Uber.
See our docs to get started and/or
learn more about Fx.
Installation
Use Go modules to install Fx in your application.
go get go.uber.org/fx@v1
Getting started
To get started with Fx, start here.
Stability
This library is v1 and follows SemVer strictly.
No breaking changes will be made to exported APIs before v2.0.0.
This project follows the Go Release Policy. Each major
version of Go is supported until there are two newer major releases.
Stargazers over time
Expand ?
Collapse ?
Documentation
?
Rendered for
linux/amd64
windows/amd64
darwin/amd64
js/wasm
Overview ?
Testing Fx Applications
Package fx is a framework that makes it easy to build applications out of
reusable, composable modules.
Fx applications use dependency injection to eliminate globals without the
tedium of manually wiring together function calls. Unlike other approaches
to dependency injection, Fx works with plain Go functions: you don't need
to use struct tags or embed special types, so Fx automatically works well
with most Go packages.
Basic usage is explained in the package-level example below. If you're new
to Fx, start there! Advanced features, including named instances, optional
parameters, and value groups, are explained under the In and Out types.
Testing Fx Applications ?To test functions that use the Lifecycle type or to write end-to-end tests
of your Fx application, use the helper functions and types provided by the
go.uber.org/fx/fxtest package.
Example ?
package main
import (
"context"
"log"
"net"
"net/http"
"os"
"time"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
)
// NewLogger constructs a logger. It's just a regular Go function, without any
// special relationship to Fx.
//
// Since it returns a *log.Logger, Fx will treat NewLogger as the constructor
// function for the standard library's logger. (We'll see how to integrate
// NewLogger into an Fx application in the main function.) Since NewLogger
// doesn't have any parameters, Fx will infer that loggers don't depend on any
// other types - we can create them from thin air.
//
// Fx calls constructors lazily, so NewLogger will only be called only if some
// other function needs a logger. Once instantiated, the logger is cached and
// reused - within the application, it's effectively a singleton.
//
// By default, Fx applications only allow one constructor for each type. See
// the documentation of the In and Out types for ways around this restriction.
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
// NewHandler constructs a simple HTTP handler. Since it returns an
// http.Handler, Fx will treat NewHandler as the constructor for the
// http.Handler type.
//
// Like many Go functions, NewHandler also returns an error. If the error is
// non-nil, Go convention tells the caller to assume that NewHandler failed
// and the other returned values aren't safe to use. Fx understands this
// idiom, and assumes that any function whose last return value is an error
// follows this convention.
//
// Unlike NewLogger, NewHandler has formal parameters. Fx will interpret these
// parameters as dependencies: in order to construct an HTTP handler,
// NewHandler needs a logger. If the application has access to a *log.Logger
// constructor (like NewLogger above), it will use that constructor or its
// cached output and supply a logger to NewHandler. If the application doesn't
// know how to construct a logger and needs an HTTP handler, it will fail to
// start.
//
// Functions may also return multiple objects. For example, we could combine
// NewHandler and NewLogger into a single function:
//
// func NewHandlerAndLogger() (*log.Logger, http.Handler, error)
//
// Fx also understands this idiom, and would treat NewHandlerAndLogger as the
// constructor for both the *log.Logger and http.Handler types. Just like
// constructors for a single type, NewHandlerAndLogger would be called at most
// once, and both the handler and the logger would be cached and reused as
// necessary.
func NewHandler(logger *log.Logger) (http.Handler, error) {
logger.Print("Executing NewHandler.")
return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
logger.Print("Got a request.")
}), nil
}
// NewMux constructs an HTTP mux. Like NewHandler, it depends on *log.Logger.
// However, it also depends on the Fx-specific Lifecycle interface.
//
// A Lifecycle is available in every Fx application. It lets objects hook into
// the application's start and stop phases. In a non-Fx application, the main
// function often includes blocks like this:
//
// srv, err := NewServer() // some long-running network server
// if err != nil {
// log.Fatalf("failed to construct server: %v", err)
// }
// // Construct other objects as necessary.
// go srv.Start()
// defer srv.Stop()
//
// In this example, the programmer explicitly constructs a bunch of objects,
// crashing the program if any of the constructors encounter unrecoverable
// errors. Once all the objects are constructed, we start any background
// goroutines and defer cleanup functions.
//
// Fx removes the manual object construction with dependency injection. It
// replaces the inline goroutine spawning and deferred cleanups with the
// Lifecycle type.
//
// Here, NewMux makes an HTTP mux available to other functions. Since
// constructors are called lazily, we know that NewMux won't be called unless
// some other function wants to register a handler. This makes it easy to use
// Fx's Lifecycle to start an HTTP server only if we have handlers registered.
func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
logger.Print("Executing NewMux.")
// First, we construct the mux and server. We don't want to start the server
// until all handlers are registered.
mux := http.NewServeMux()
server := &http.Server{
Addr: "127.0.0.1:8080",
Handler: mux,
}
// If NewMux is called, we know that another function is using the mux. In
// that case, we'll use the Lifecycle type to register a Hook that starts
// and stops our HTTP server.
//
// Hooks are executed in dependency order. At startup, NewLogger's hooks run
// before NewMux's. On shutdown, the order is reversed.
//
// Returning an error from OnStart hooks interrupts application startup. Fx
// immediately runs the OnStop portions of any successfully-executed OnStart
// hooks (so that types which started cleanly can also shut down cleanly),
// then exits.
//
// Returning an error from OnStop hooks logs a warning, but Fx continues to
// run the remaining hooks.
lc.Append(fx.Hook{
// To mitigate the impact of deadlocks in application startup and
// shutdown, Fx imposes a time limit on OnStart and OnStop hooks. By
// default, hooks have a total of 15 seconds to complete. Timeouts are
// passed via Go's usual context.Context.
OnStart: func(context.Context) error {
logger.Print("Starting HTTP server.")
ln, err := net.Listen("tcp", server.Addr)
if err != nil {
return err
}
go server.Serve(ln)
return nil
},
OnStop: func(ctx context.Context) error {
logger.Print("Stopping HTTP server.")
return server.Shutdown(ctx)
},
})
return mux
}
// Register mounts our HTTP handler on the mux.
//
// Register is a typical top-level application function: it takes a generic
// type like ServeMux, which typically comes from a third-party library, and
// introduces it to a type that contains our application logic. In this case,
// that introduction consists of registering an HTTP handler. Other typical
// examples include registering RPC procedures and starting queue consumers.
//
// Fx calls these functions invocations, and they're treated differently from
// the constructor functions above. Their arguments are still supplied via
// dependency injection and they may still return an error to indicate
// failure, but any other return values are ignored.
//
// Unlike constructors, invocations are called eagerly. See the main function
// below for details.
func Register(mux *http.ServeMux, h http.Handler) {
mux.Handle("/", h)
}
func main() {
app := fx.New(
// Provide all the constructors we need, which teaches Fx how we'd like to
// construct the *log.Logger, http.Handler, and *http.ServeMux types.
// Remember that constructors are called lazily, so this block doesn't do
// much on its own.
fx.Provide(
NewLogger,
NewHandler,
NewMux,
),
// Since constructors are called lazily, we need some invocations to
// kick-start our application. In this case, we'll use Register. Since it
// depends on an http.Handler and *http.ServeMux, calling it requires Fx
// to build those types using the constructors above. Since we call
// NewMux, we also register Lifecycle hooks to start and stop an HTTP
// server.
fx.Invoke(Register),
// This is optional. With this, you can control where Fx logs
// its events. In this case, we're using a NopLogger to keep
// our test silent. Normally, you'll want to use an
// fxevent.ZapLogger or an fxevent.ConsoleLogger.
fx.WithLogger(
func() fxevent.Logger {
return fxevent.NopLogger
},
),
)
// In a typical application, we could just use app.Run() here. Since we
// don't want this example to run forever, we'll use the more-explicit Start
// and Stop.
startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil {
log.Fatal(err)
}
// Normally, we'd block here with <-app.Done(). Instead, we'll make an HTTP
// request to demonstrate that our server is running.
if _, err := http.Get("http://localhost:8080/"); err != nil {
log.Fatal(err)
}
stopCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Stop(stopCtx); err != nil {
log.Fatal(err)
}
}
Output:
Executing NewLogger.
Executing NewMux.
Executing NewHandler.
Starting HTTP server.
Got a request.
Stopping HTTP server.
Share
Format
Run
Index ?
Constants
Variables
func Annotate(t interface{}, anns ...Annotation) interface{}
func ValidateApp(opts ...Option) error
func VisualizeError(err error) (string, error)
type Annotated
func (a Annotated) String() string
type Annotation
func As(interfaces ...interface{}) Annotation
func From(interfaces ...interface{}) Annotation
func OnStart(onStart interface{}) Annotation
func OnStop(onStop interface{}) Annotation
func ParamTags(tags ...string) Annotation
func ResultTags(tags ...string) Annotation
type App
func New(opts ...Option) *App
func (app *App) Done() <-chan os.Signal
func (app *App) Err() error
func (app *App) Run()
func (app *App) Start(ctx context.Context) (err error)
func (app *App) StartTimeout() time.Duration
func (app *App) Stop(ctx context.Context) (err error)
func (app *App) StopTimeout() time.Duration
func (app *App) Wait() <-chan ShutdownSignal
type DotGraph
type ErrorHandler
type Hook
func StartHook[T HookFunc](start T) Hook
func StartStopHook[T1 HookFunc, T2 HookFunc](start T1, stop T2) Hook
func StopHook[T HookFunc](stop T) Hook
type HookFunc
type In
type Lifecycle
type Option
func Decorate(decorators ...interface{}) Option
func Error(errs ...error) Option
func ErrorHook(funcs ...ErrorHandler) Option
func Extract(target interface{}) Optiondeprecated
func Invoke(funcs ...interface{}) Option
func Logger(p Printer) Option
func Module(name string, opts ...Option) Option
func Options(opts ...Option) Option
func Populate(targets ...interface{}) Option
func Provide(constructors ...interface{}) Option
func RecoverFromPanics() Option
func Replace(values ...interface{}) Option
func StartTimeout(v time.Duration) Option
func StopTimeout(v time.Duration) Option
func Supply(values ...interface{}) Option
func WithLogger(constructor interface{}) Option
type Out
type Printer
type ShutdownOption
func ExitCode(code int) ShutdownOption
func ShutdownTimeout(timeout time.Duration) ShutdownOptiondeprecated
type ShutdownSignal
func (sig ShutdownSignal) String() string
type Shutdowner
Examples ?
Package
Error
Populate
Constants ?
View Source
const DefaultTimeout = 15 * time.Second
DefaultTimeout is the default timeout for starting or stopping an
application. It can be configured with the StartTimeout and StopTimeout
options.
View Source
const Version = "1.20.1"
Version is exported for runtime compatibility checks.
Variables ?
View Source
var NopLogger = WithLogger(func() fxevent.Logger { return fxevent.NopLogger })
NopLogger disables the application's log output. Note that this makes some
failures difficult to debug, since no errors are printed to console.
View Source
var Private = privateOption{}
Private is an option that can be passed as an argument to Provide to
restrict access to the constructors being provided. Specifically,
corresponding constructors can only be used within the current module
or modules the current module contains. Other modules that contain this
module won't be able to use the constructor.
For example, the following would fail because the app doesn't have access
to the inner module's constructor.
fx.New(
fx.Module("SubModule", fx.Provide(func() int { return 0 }, fx.Private)),
fx.Invoke(func(a int) {}),
)
Functions ?
func Annotate ?
added in
v1.15.0
func Annotate(t interface{}, anns ...Annotation) interface{}
Variadic functions
Annotate lets you annotate a function's parameters and returns
without you having to declare separate struct definitions for them.
For example,
func NewGateway(ro, rw *db.Conn) *Gateway { ... }
fx.Provide(
fx.Annotate(
NewGateway,
fx.ParamTags(`name:"ro" optional:"true"`, `name:"rw"`),
fx.ResultTags(`name:"foo"`),
),
)
Is equivalent to,
type params struct {
fx.In
RO *db.Conn `name:"ro" optional:"true"`
RW *db.Conn `name:"rw"`
}
type result struct {
fx.Out
GW *Gateway `name:"foo"`
}
fx.Provide(func(p params) result {
return result{GW: NewGateway(p.RO, p.RW)}
})
Using the same annotation multiple times is invalid.
For example, the following will fail with an error:
fx.Provide(
fx.Annotate(
NewGateWay,
fx.ParamTags(`name:"ro" optional:"true"`),
fx.ParamTags(`name:"rw"), // ERROR: ParamTags was already used above
fx.ResultTags(`name:"foo"`)
)
)
is considered an invalid usage and will not apply any of the
Annotations to NewGateway.
If more tags are given than the number of parameters/results, only
the ones up to the number of parameters/results will be applied.
Variadic functions ?If the provided function is variadic, Annotate treats its parameter as a
slice. For example,
fx.Annotate(func(w io.Writer, rs ...io.Reader) {
// ...
}, ...)
Is equivalent to,
fx.Annotate(func(w io.Writer, rs []io.Reader) {
// ...
}, ...)
You can use variadic parameters with Fx's value groups.
For example,
fx.Annotate(func(mux *http.ServeMux, handlers ...http.Handler) {
// ...
}, fx.ParamTags(``, `group:"server"`))
If we provide the above to the application,
any constructor in the Fx application can inject its HTTP handlers
by using fx.Annotate, fx.Annotated, or fx.Out.
fx.Annotate(
func(..) http.Handler { ... },
fx.ResultTags(`group:"server"`),
)
fx.Annotated{
Target: func(..) http.Handler { ... },
Group: "server",
}
func ValidateApp ?
added in
v1.13.0
func ValidateApp(opts ...Option) error
ValidateApp validates that supplied graph would run and is not missing any dependencies. This
method does not invoke actual input functions.
func VisualizeError ?
added in
v1.7.0
func VisualizeError(err error) (string, error)
VisualizeError returns the visualization of the error if available.
Types ?
type Annotated ?
added in
v1.9.0
type Annotated struct {
// If specified, this will be used as the name for all non-error values returned
// by the constructor. For more information on named values, see the documentation
// for the fx.Out type.
//
// A name option may not be provided if a group option is provided.
Name string
// If specified, this will be used as the group name for all non-error values returned
// by the constructor. For more information on value groups, see the package documentation.
//
// A group option may not be provided if a name option is provided.
//
// Similar to group tags, the group name may be followed by a `,flatten`
// option to indicate that each element in the slice returned by the
// constructor should be injected into the value group individually.
Group string
// Target is the constructor or value being annotated with fx.Annotated.
Target interface{}
}
Annotated annotates a constructor provided to Fx with additional options.
For example,
func NewReadOnlyConnection(...) (*Connection, error)
fx.Provide(fx.Annotated{
Name: "ro",
Target: NewReadOnlyConnection,
})
Is equivalent to,
type result struct {
fx.Out
Connection *Connection `name:"ro"`
}
fx.Provide(func(...) (result, error) {
conn, err := NewReadOnlyConnection(...)
return result{Connection: conn}, err
})
Annotated cannot be used with constructors which produce fx.Out objects.
When used with fx.Supply, the target is a value rather than a constructor function.
func (Annotated) String ?
added in
v1.10.0
func (a Annotated) String() string
type Annotation ?
added in
v1.15.0
type Annotation interface {
// contains filtered or unexported methods
}
Annotation can be passed to Annotate(f interface{}, anns ...Annotation)
for annotating the parameter and result types of a function.
func As ?
added in
v1.15.0
func As(interfaces ...interface{}) Annotation
As is an Annotation that annotates the result of a function (i.e. a
constructor) to be provided as another interface.
For example, the following code specifies that the return type of
bytes.NewBuffer (bytes.Buffer) should be provided as io.Writer type:
fx.Provide(
fx.Annotate(bytes.NewBuffer(...), fx.As(new(io.Writer)))
)
In other words, the code above is equivalent to:
fx.Provide(func() io.Writer {
return bytes.NewBuffer()
// provides io.Writer instead of *bytes.Buffer
})
Note that the bytes.Buffer type is provided as an io.Writer type, so this
constructor does NOT provide both bytes.Buffer and io.Writer type; it just
provides io.Writer type.
When multiple values are returned by the annotated function, each type
gets mapped to corresponding positional result of the annotated function.
For example,
func a() (bytes.Buffer, bytes.Buffer) {
...
}
fx.Provide(
fx.Annotate(a, fx.As(new(io.Writer), new(io.Reader)))
)
Is equivalent to,
fx.Provide(func() (io.Writer, io.Reader) {
w, r := a()
return w, r
}
As annotation cannot be used in a function that returns an Out struct as a return type.
func From ?
added in
v1.19.0
func From(interfaces ...interface{}) Annotation
From is an Annotation that annotates the parameter(s) for a function (i.e. a
constructor) to be accepted from other provided types. It is analogous to the
As for parameter types to the constructor.
For example,
type Runner interface { Run() }
func NewFooRunner() *FooRunner // implements Runner
func NewRunnerWrap(r Runner) *RunnerWrap
fx.Provide(
fx.Annotate(
NewRunnerWrap,
fx.From(new(*FooRunner)),
),
)
Is equivalent to,
fx.Provide(func(r *FooRunner) *RunnerWrap {
// need *FooRunner instead of Runner
return NewRunnerWrap(r)
})
When the annotated function takes in multiple parameters, each type gets
mapped to corresponding positional parameter of the annotated function
For example,
func NewBarRunner() *BarRunner // implements Runner
func NewRunnerWraps(r1 Runner, r2 Runner) *RunnerWraps
fx.Provide(
fx.Annotate(
NewRunnerWraps,
fx.From(new(*FooRunner), new(*BarRunner)),
),
)
Is equivalent to,
fx.Provide(func(r1 *FooRunner, r2 *BarRunner) *RunnerWraps {
return NewRunnerWraps(r1, r2)
})
From annotation cannot be used in a function that takes an In struct as a
parameter.
func OnStart ?
added in
v1.18.0
func OnStart(onStart interface{}) Annotation
OnStart is an Annotation that appends an OnStart Hook to the application
Lifecycle when that function is called. This provides a way to create
Lifecycle OnStart (see Lifecycle type documentation) hooks without building a
function that takes a dependency on the Lifecycle type.
fx.Provide(
fx.Annotate(
NewServer,
fx.OnStart(func(ctx context.Context, server Server) error {
return server.Listen(ctx)
}),
)
)
Which is functionally the same as:
fx.Provide(
func(lifecycle fx.Lifecycle, p Params) Server {
server := NewServer(p)
lifecycle.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
return server.Listen(ctx)
},
})
return server
}
)
It is also possible to use OnStart annotation with other parameter and result
annotations, provided that the parameter of the function passed to OnStart
matches annotated parameters and results.
For example, the following is possible:
fx.Provide(
fx.Annotate(
func (a A) B {...},
fx.ParamTags(`name:"A"`),
fx.ResultTags(`name:"B"`),
fx.OnStart(func (p OnStartParams) {...}),
),
)
As long as OnStartParams looks like the following and has no other dependencies
besides Context or Lifecycle:
type OnStartParams struct {
fx.In
FieldA A `name:"A"`
FieldB B `name:"B"`
}
Only one OnStart annotation may be applied to a given function at a time,
however functions may be annotated with other types of lifecycle Hooks, such
as OnStop. The hook function passed into OnStart cannot take any arguments
outside of the annotated constructor's existing dependencies or results, except
a context.Context.
func OnStop ?
added in
v1.18.0
func OnStop(onStop interface{}) Annotation
OnStop is an Annotation that appends an OnStop Hook to the application
Lifecycle when that function is called. This provides a way to create
Lifecycle OnStop (see Lifecycle type documentation) hooks without building a
function that takes a dependency on the Lifecycle type.
fx.Provide(
fx.Annotate(
NewServer,
fx.OnStop(func(ctx context.Context, server Server) error {
return server.Shutdown(ctx)
}),
)
)
Which is functionally the same as:
fx.Provide(
func(lifecycle fx.Lifecycle, p Params) Server {
server := NewServer(p)
lifecycle.Append(fx.Hook{
OnStop: func(ctx context.Context) error {
return server.Shutdown(ctx)
},
})
return server
}
)
It is also possible to use OnStop annotation with other parameter and result
annotations, provided that the parameter of the function passed to OnStop
matches annotated parameters and results.
For example, the following is possible:
fx.Provide(
fx.Annotate(
func (a A) B {...},
fx.ParamTags(`name:"A"`),
fx.ResultTags(`name:"B"`),
fx.OnStop(func (p OnStopParams) {...}),
),
)
As long as OnStopParams looks like the following and has no other dependencies
besides Context or Lifecycle:
type OnStopParams struct {
fx.In
FieldA A `name:"A"`
FieldB B `name:"B"`
}
Only one OnStop annotation may be applied to a given function at a time,
however functions may be annotated with other types of lifecycle Hooks, such
as OnStart. The hook function passed into OnStop cannot take any arguments
outside of the annotated constructor's existing dependencies or results, except
a context.Context.
func ParamTags ?
added in
v1.15.0
func ParamTags(tags ...string) Annotation
ParamTags is an Annotation that annotates the parameter(s) of a function.
When multiple tags are specified, each tag is mapped to the corresponding
positional parameter.
ParamTags cannot be used in a function that takes an fx.In struct as a
parameter.
func ResultTags ?
added in
v1.15.0
func ResultTags(tags ...string) Annotation
ResultTags is an Annotation that annotates the result(s) of a function.
When multiple tags are specified, each tag is mapped to the corresponding
positional result.
ResultTags cannot be used on a function that returns an fx.Out struct.
type App ?
type App struct {
// contains filtered or unexported fields
}
An App is a modular application built around dependency injection. Most
users will only need to use the New constructor and the all-in-one Run
convenience method. In more unusual cases, users may need to use the Err,
Start, Done, and Stop methods by hand instead of relying on Run.
New creates and initializes an App. All applications begin with a
constructor for the Lifecycle type already registered.
In addition to that built-in functionality, users typically pass a handful
of Provide options and one or more Invoke options. The Provide options
teach the application how to instantiate a variety of types, and the Invoke
options describe how to initialize the application.
When created, the application immediately executes all the functions passed
via Invoke options. To supply these functions with the parameters they
need, the application looks for constructors that return the appropriate
types; if constructors for any required types are missing or any
invocations return an error, the application will fail to start (and Err
will return a descriptive error message).
Once all the invocations (and any required constructors) have been called,
New returns and the application is ready to be started using Run or Start.
On startup, it executes any OnStart hooks registered with its Lifecycle.
OnStart hooks are executed one at a time, in order, and must all complete
within a configurable deadline (by default, 15 seconds). For details on the
order in which OnStart hooks are executed, see the documentation for the
Start method.
At this point, the application has successfully started up. If started via
Run, it will continue operating until it receives a shutdown signal from
Done (see the Done documentation for details); if started explicitly via
Start, it will operate until the user calls Stop. On shutdown, OnStop hooks
execute one at a time, in reverse order, and must all complete within a
configurable deadline (again, 15 seconds by default).
func New ?
func New(opts ...Option) *App
New creates and initializes an App, immediately executing any functions
registered via Invoke options. See the documentation of the App struct for
details on the application's initialization, startup, and shutdown logic.
func (*App) Done ?
func (app *App) Done() <-chan os.Signal
Done returns a channel of signals to block on after starting the
application. Applications listen for the SIGINT and SIGTERM signals; during
development, users can send the application SIGTERM by pressing Ctrl-C in
the same terminal as the running process.
Alternatively, a signal can be broadcast to all done channels manually by
using the Shutdown functionality (see the Shutdowner documentation for details).
Note: The channel Done returns will not receive a signal unless the application
as been started via Start or Run.
func (*App) Err ?
func (app *App) Err() error
Err returns any error encountered during New's initialization. See the
documentation of the New method for details, but typical errors include
missing constructors, circular dependencies, constructor errors, and
invocation errors.
Most users won't need to use this method, since both Run and Start
short-circuit if initialization failed.
func (*App) Run ?
func (app *App) Run()
Run starts the application, blocks on the signals channel, and then
gracefully shuts the application down. It uses DefaultTimeout to set a
deadline for application startup and shutdown, unless the user has
configured different timeouts with the StartTimeout or StopTimeout options.
It's designed to make typical applications simple to run.
However, all of Run's functionality is implemented in terms of the exported
Start, Done, and Stop methods. Applications with more specialized needs
can use those methods directly instead of relying on Run.
func (*App) Start ?
func (app *App) Start(ctx context.Context) (err error)
Start kicks off all long-running goroutines, like network servers or
message queue consumers. It does this by interacting with the application's
Lifecycle.
By taking a dependency on the Lifecycle type, some of the user-supplied
functions called during initialization may have registered start and stop
hooks. Because initialization calls constructors serially and in dependency
order, hooks are naturally registered in serial and dependency order too.
Start executes all OnStart hooks registered with the application's
Lifecycle, one at a time and in order. This ensures that each constructor's
start hooks aren't executed until all its dependencies' start hooks
complete. If any of the start hooks return an error, Start short-circuits,
calls Stop, and returns the inciting error.
Note that Start short-circuits immediately if the New constructor
encountered any errors in application initialization.
func (*App) StartTimeout ?
added in
v1.5.0
func (app *App) StartTimeout() time.Duration
StartTimeout returns the configured startup timeout. Apps default to using
DefaultTimeout, but users can configure this behavior using the
StartTimeout option.
func (*App) Stop ?
func (app *App) Stop(ctx context.Context) (err error)
Stop gracefully stops the application. It executes any registered OnStop
hooks in reverse order, so that each constructor's stop hooks are called
before its dependencies' stop hooks.
If the application didn't start cleanly, only hooks whose OnStart phase was
called are executed. However, all those hooks are executed, even if some
fail.
func (*App) StopTimeout ?
added in
v1.5.0
func (app *App) StopTimeout() time.Duration
StopTimeout returns the configured shutdown timeout. Apps default to using
DefaultTimeout, but users can configure this behavior using the StopTimeout
option.
func (*App) Wait ?
added in
v1.19.0
func (app *App) Wait() <-chan ShutdownSignal
Wait returns a channel of ShutdownSignal to block on after starting the
application and function, similar to App.Done, but with a minor difference.
Should an ExitCode be provided as a ShutdownOption to
the Shutdowner Shutdown method, the exit code will be available as part
of the ShutdownSignal struct.
Should the app receive a SIGTERM or SIGINT, the given
signal will be populated in the ShutdownSignal struct.
type DotGraph ?
added in
v1.7.0
type DotGraph string
DotGraph contains a DOT language visualization of the dependency graph in
an Fx application. It is provided in the container by default at
initialization. On failure to build the dependency graph, it is attached
to the error and if possible, colorized to highlight the root cause of the
failure.
type ErrorHandler ?
added in
v1.7.0
type ErrorHandler interface {
HandleError(error)
}
ErrorHandler handles Fx application startup errors.
type Hook ?
type Hook struct {
OnStart func(context.Context) error
OnStop func(context.Context) error
// contains filtered or unexported fields
}
A Hook is a pair of start and stop callbacks, either of which can be nil.
If a Hook's OnStart callback isn't executed (because a previous OnStart
failure short-circuited application startup), its OnStop callback won't be
executed.
func StartHook ?
added in
v1.19.0
func StartHook[T HookFunc](start T) Hook
StartHook returns a new Hook with start as its [Hook.OnStart] function,
wrapping its signature as needed. For example, given the following function:
func myhook() {
fmt.Println("hook called")
}
then calling:
lifecycle.Append(StartHook(myfunc))
is functionally equivalent to calling:
lifecycle.Append(fx.Hook{
OnStart: func(context.Context) error {
myfunc()
return nil
},
})
The same is true for all functions that satisfy the HookFunc constraint.
Note that any context.Context parameter or error return will be propagated
as expected. If propagation is not intended, users should instead provide a
closure that discards the undesired value(s), or construct a Hook directly.
func StartStopHook ?
added in
v1.19.0
func StartStopHook[T1 HookFunc, T2 HookFunc](start T1, stop T2) Hook
StartStopHook returns a new Hook with start as its [Hook.OnStart] function
and stop as its [Hook.OnStop] function, independently wrapping the signature
of each as needed.
func StopHook ?
added in
v1.19.0
func StopHook[T HookFunc](stop T) Hook
StopHook returns a new Hook with stop as its [Hook.OnStop] function,
wrapping its signature as needed. For example, given the following function:
func myhook() {
fmt.Println("hook called")
}
then calling:
lifecycle.Append(StopHook(myfunc))
is functionally equivalent to calling:
lifecycle.Append(fx.Hook{
OnStop: func(context.Context) error {
myfunc()
return nil
},
})
The same is true for all functions that satisfy the HookFunc constraint.
Note that any context.Context parameter or error return will be propagated
as expected. If propagation is not intended, users should instead provide a
closure that discards the undesired value(s), or construct a Hook directly.
type HookFunc ?
added in
v1.19.0
type HookFunc interface {
~func() | ~func() error | ~func(context.Context) | ~func(context.Context) error
}
A HookFunc is a function that can be used as a Hook.
type In ?
type In = dig.In
Parameter Structs
Optional Dependencies
Named Values
Value Groups
Soft Value Groups
Unexported fields
In can be embedded in a constructor's parameter struct to take advantage of
advanced dependency injection features.
Modules should take a single parameter struct that embeds an In in order to
provide a forward-compatible API: since adding fields to a struct is
backward-compatible, modules can then add optional dependencies in minor
releases.
Parameter Structs ?Fx constructors declare their dependencies as function parameters. This can
quickly become unreadable if the constructor has a lot of dependencies.
func NewHandler(users *UserGateway, comments *CommentGateway, posts *PostGateway, votes *VoteGateway, authz *AuthZGateway) *Handler {
// ...
}
To improve the readability of constructors like this, create a struct that
lists all the dependencies as fields and change the function to accept that
struct instead. The new struct is called a parameter struct.
Fx has first class support for parameter structs: any struct embedding
fx.In gets treated as a parameter struct, so the individual fields in the
struct are supplied via dependency injection. Using a parameter struct, we
can make the constructor above much more readable:
type HandlerParams struct {
fx.In
Users *UserGateway
Comments *CommentGateway
Posts *PostGateway
Votes *VoteGateway
AuthZ *AuthZGateway
}
func NewHandler(p HandlerParams) *Handler {
// ...
}
Though it's rarely a good idea, constructors can receive any combination of
parameter structs and parameters.
func NewHandler(p HandlerParams, l *log.Logger) *Handler {
// ...
}
Optional Dependencies ?Constructors often have optional dependencies on some types: if those types are
missing, they can operate in a degraded state. Fx supports optional
dependencies via the `optional:"true"` tag to fields on parameter structs.
type UserGatewayParams struct {
fx.In
Conn *sql.DB
Cache *redis.Client `optional:"true"`
}
If an optional field isn't available in the container, the constructor
receives the field's zero value.
func NewUserGateway(p UserGatewayParams, log *log.Logger) (*UserGateway, error) {
if p.Cache == nil {
log.Print("Caching disabled")
}
// ...
}
Constructors that declare optional dependencies MUST gracefully handle
situations in which those dependencies are absent.
The optional tag also allows adding new dependencies without breaking
existing consumers of the constructor.
Named Values ?Some use cases require the application container to hold multiple values of
the same type. For details on producing named values, see the documentation
for the Out type.
Fx allows functions to consume named values via the `name:".."` tag on
parameter structs. Note that both the name AND type of the fields on the
parameter struct must match the corresponding result struct.
type GatewayParams struct {
fx.In
WriteToConn *sql.DB `name:"rw"`
ReadFromConn *sql.DB `name:"ro"`
}
The name tag may be combined with the optional tag to declare the
dependency optional.
type GatewayParams struct {
fx.In
WriteToConn *sql.DB `name:"rw"`
ReadFromConn *sql.DB `name:"ro" optional:"true"`
}
func NewCommentGateway(p GatewayParams, log *log.Logger) (*CommentGateway, error) {
if p.ReadFromConn == nil {
log.Print("Warning: Using RW connection for reads")
p.ReadFromConn = p.WriteToConn
}
// ...
}
Value Groups ?To make it easier to produce and consume many values of the same type, Fx
supports named, unordered collections called value groups. For details on
producing value groups, see the documentation for the Out type.
Functions can depend on a value group by requesting a slice tagged with
`group:".."`. This will execute all constructors that provide a value to
that group in an unspecified order, then collect all the results into a
single slice. Keep in mind that this makes the types of the parameter and
result struct fields different: if a group of constructors each returns
type T, parameter structs consuming the group must use a field of type []T.
type ServerParams struct {
fx.In
Handlers []Handler `group:"server"`
}
func NewServer(p ServerParams) *Server {
server := newServer()
for _, h := range p.Handlers {
server.Register(h)
}
return server
}
Note that values in a value group are unordered. Fx makes no guarantees
about the order in which these values will be produced.
Soft Value Groups ?A soft value group can be thought of as a best-attempt at populating the
group with values from constructors that have already run. In other words,
if a constructor's output type is only consumed by a soft value group,
it will not be run.
Note that Fx does not guarantee precise execution order of constructors
or invokers, which means that the change in code that affects execution
ordering of other constructors or functions will affect the values
populated in this group.
To declare a soft relationship between a group and its constructors, use
the `soft` option on the group tag (`group:"[groupname],soft"`).
This option is only valid for input parameters.
type Params struct {
fx.In
Handlers []Handler `group:"server,soft"`
Logger *zap.Logger
}
NewHandlerAndLogger := func() (Handler, *zap.Logger) { ... }
NewHandler := func() Handler { ... }
Foo := func(Params) { ... }
app := fx.New(
fx.Provide(fx.Annotate(NewHandlerAndLogger, fx.ResultTags(`group:"server"`))),
fx.Provide(fx.Annotate(NewHandler, fx.ResultTags(`group::"server"`))),
fx.Invoke(Foo),
)
The only constructor called is `NewHandlerAndLogger`, because this also provides
`*zap.Logger` needed in the `Params` struct received by `Foo`. The Handlers
group will be populated with a single Handler returned by `NewHandlerAndLogger`.
In the next example, the slice `s` isn't populated as the provider would be
called only because `strings` soft group value is its only consumer.
app := fx.New(
fx.Provide(
fx.Annotate(
func() (string, int) { return "hello", 42 },
fx.ResultTags(`group:"strings"`),
),
),
fx.Invoke(
fx.Annotate(func(s []string) {
// s will be an empty slice
}, fx.ParamTags(`group:"strings,soft"`)),
),
)
In the next example, the slice `s` will be populated because there is a
consumer for the same type which is not a `soft` dependency.
app := fx.New(
fx.Provide(
fx.Annotate(
func() string { "hello" },
fx.ResultTags(`group:"strings"`),
),
),
fx.Invoke(
fx.Annotate(func(b []string) {
// b is []string{"hello"}
}, fx.ParamTags(`group:"strings"`)),
),
fx.Invoke(
fx.Annotate(func(s []string) {
// s is []string{"hello"}
}, fx.ParamTags(`group:"strings,soft"`)),
),
)
Unexported fields ?By default, a type that embeds fx.In may not have any unexported fields. The
following will return an error if used with Fx.
type Params struct {
fx.In
Logger *zap.Logger
mu sync.Mutex
}
If you have need of unexported fields on such a type, you may opt-into
ignoring unexported fields by adding the ignore-unexported struct tag to the
fx.In. For example,
type Params struct {
fx.In `ignore-unexported:"true"`
Logger *zap.Logger
mu sync.Mutex
}
type Lifecycle ?
type Lifecycle interface {
Append(Hook)
}
Lifecycle allows constructors to register callbacks that are executed on
application start and stop. See the documentation for App for details on Fx
applications' initialization, startup, and shutdown logic.
type Option ?
type Option interface {
fmt.Stringer
// contains filtered or unexported methods
}
An Option configures an App using the functional options paradigm
popularized by Rob Pike. If you're unfamiliar with this style, see
https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html.
func Decorate ?
added in
v1.17.0
func Decorate(decorators ...interface{}) Option
Decorator functions
Decorator scope
Decorate specifies one or more decorator functions to an Fx application.
Decorator functions ?Decorator functions let users augment objects in the graph.
They can take in zero or more dependencies that must be provided to the
application with fx.Provide, and produce one or more values that can be used
by other fx.Provide and fx.Invoke calls.
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("myapp")
})
fx.Invoke(func(log *zap.Logger) {
log.Info("hello")
// Output:
// {"level": "info","logger":"myapp","msg":"hello"}
})
The following decorator accepts multiple dependencies from the graph,
augments and returns one of them.
fx.Decorate(func(log *zap.Logger, cfg *Config) *zap.Logger {
return log.Named(cfg.Name)
})
Similar to fx.Provide, functions passed to fx.Decorate may optionally return
an error as their last result.
If a decorator returns a non-nil error, it will halt application startup.
fx.Decorate(func(conn *sql.DB, cfg *Config) (*sql.DB, error) {
if err := conn.Ping(); err != nil {
return sql.Open("driver-name", cfg.FallbackDB)
}
return conn, nil
})
Decorators support both, fx.In and fx.Out structs, similar to fx.Provide and
fx.Invoke.
type Params struct {
fx.In
Client usersvc.Client `name:"readOnly"`
}
type Result struct {
fx.Out
Client usersvc.Client `name:"readOnly"`
}
fx.Decorate(func(p Params) Result {
...
})
Decorators can be annotated with the fx.Annotate function, but not with the
fx.Annotated type. Refer to documentation on fx.Annotate() to learn how to
use it for annotating functions.
fx.Decorate(
fx.Annotate(
func(client usersvc.Client) usersvc.Client {
// ...
},
fx.ParamTags(`name:"readOnly"`),
fx.ResultTags(`name:"readOnly"`),
),
)
Decorators support augmenting, filtering, or replacing value groups.
To decorate a value group, expect the entire value group slice and produce
the new slice.
type HandlerParam struct {
fx.In
Log *zap.Logger
Handlers []Handler `group:"server"
}
type HandlerResult struct {
fx.Out
Handlers []Handler `group:"server"
}
fx.Decorate(func(p HandlerParam) HandlerResult {
var r HandlerResult
for _, handler := range p.Handlers {
r.Handlers = append(r.Handlers, wrapWithLogger(p.Log, handler))
}
return r
}),
Decorator scope ?Modifications made to the Fx graph with fx.Decorate are scoped to the
deepest fx.Module inside which the decorator was specified.
fx.Module("mymodule",
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("myapp")
}),
fx.Invoke(func(log *zap.Logger) {
log.Info("decorated logger")
// Output:
// {"level": "info","logger":"myapp","msg":"decorated logger"}
}),
),
fx.Invoke(func(log *zap.Logger) {
log.Info("plain logger")
// Output:
// {"level": "info","msg":"plain logger"}
}),
Decorations specified in the top-level fx.New call apply across the
application and chain with module-specific decorators.
fx.New(
// ...
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.With(zap.Field("service", "myservice"))
}),
// ...
fx.Invoke(func(log *zap.Logger) {
log.Info("outer decorator")
// Output:
// {"level": "info","service":"myservice","msg":"outer decorator"}
}),
// ...
fx.Module("mymodule",
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("myapp")
}),
fx.Invoke(func(log *zap.Logger) {
log.Info("inner decorator")
// Output:
// {"level": "info","logger":"myapp","service":"myservice","msg":"inner decorator"}
}),
),
)
func Error ?
added in
v1.6.0
func Error(errs ...error) Option
Error registers any number of errors with the application to short-circuit
startup. If more than one error is given, the errors are combined into a
single error.
Similar to invocations, errors are applied in order. All Provide and Invoke
options registered before or after an Error option will not be applied.
Example ?
package main
import (
"errors"
"fmt"
"net/http"
"os"
"go.uber.org/fx"
)
func main() {
// A module that provides a HTTP server depends on
// the $PORT environment variable. If the variable
// is unset, the module returns an fx.Error option.
newHTTPServer := func() fx.Option {
port := os.Getenv("PORT")
if port == "" {
return fx.Error(errors.New("$PORT is not set"))
}
return fx.Provide(&http.Server{
Addr: fmt.Sprintf("127.0.0.1:%s", port),
})
}
app := fx.New(
fx.NopLogger,
newHTTPServer(),
fx.Invoke(func(s *http.Server) error { return s.ListenAndServe() }),
)
fmt.Println(app.Err())
}
Output:
$PORT is not set
Share
Format
Run
func ErrorHook ?
added in
v1.7.0
func ErrorHook(funcs ...ErrorHandler) Option
ErrorHook registers error handlers that implement error handling functions.
They are executed on invoke failures. Passing multiple ErrorHandlers appends
the new handlers to the application's existing list.
func Extract
deprecated
func Extract(target interface{}) Option
Extract fills the given struct with values from the dependency injection
container on application initialization. The target MUST be a pointer to a
struct. Only exported fields will be filled.
Deprecated: Use Populate instead.
func Invoke ?
func Invoke(funcs ...interface{}) Option
Invoke registers functions that are executed eagerly on application start.
Arguments for these invocations are built using the constructors registered
by Provide. Passing multiple Invoke options appends the new invocations to
the application's existing list.
Unlike constructors, invocations are always executed, and they're always
run in order. Invocations may have any number of returned values.
If the final returned object is an error, it indicates whether the operation
was successful.
All other returned values are discarded.
Invokes registered in [Module]s are run before the ones registered at the
scope of the parent. Invokes within the same Module is run in the order
they were provided. For example,
fx.New(
fx.Invoke(func3),
fx.Module("someModule",
fx.Invoke(func1),
fx.Invoke(func2),
),
fx.Invoke(func4),
)
invokes func1, func2, func3, func4 in that order.
Typically, invoked functions take a handful of high-level objects (whose
constructors depend on lower-level objects) and introduce them to each
other. This kick-starts the application by forcing it to instantiate a
variety of types.
To see an invocation in use, read through the package-level example. For
advanced features, including optional parameters and named instances, see
the documentation of the In and Out types.
func Logger ?
func Logger(p Printer) Option
Logger redirects the application's log output to the provided printer.
Deprecated: use WithLogger instead.
func Module ?
added in
v1.17.0
func Module(name string, opts ...Option) Option
Module is a named group of zero or more fx.Options.
A Module creates a scope in which certain operations are taken
place. For more information, see Decorate, Replace, or Invoke.
func Options ?
func Options(opts ...Option) Option
Options converts a collection of Options into a single Option. This allows
packages to bundle sophisticated functionality into easy-to-use Fx modules.
For example, a logging package might export a simple option like this:
package logging
var Module = fx.Provide(func() *log.Logger {
return log.New(os.Stdout, "", 0)
})
A shared all-in-one microservice package could then use Options to bundle
logging with similar metrics, tracing, and gRPC modules:
package server
var Module = fx.Options(
logging.Module,
metrics.Module,
tracing.Module,
grpc.Module,
)
Since this all-in-one module has a minimal API surface, it's easy to add
new functionality to it without breaking existing users. Individual
applications can take advantage of all this functionality with only one
line of code:
app := fx.New(server.Module)
Use this pattern sparingly, since it limits the user's ability to customize
their application.
func Populate ?
added in
v1.4.0
func Populate(targets ...interface{}) Option
Populate sets targets with values from the dependency injection container
during application initialization. All targets must be pointers to the
values that must be populated. Pointers to structs that embed In are
supported, which can be used to populate multiple values in a struct.
Annotating each pointer with ParamTags is also supported as a shorthand
to passing a pointer to a struct that embeds In with field tags. For example:
var a A
var b B
fx.Populate(
fx.Annotate(
&a,
fx.ParamTags(`name:"A"`)
),
fx.Annotate(
&b,
fx.ParamTags(`name:"B"`)
)
)
Code above is equivalent to the following:
type Target struct {
fx.In
a A `name:"A"`
b B `name:"B"`
}
var target Target
...
fx.Populate(&target)
This is most helpful in unit tests: it lets tests leverage Fx's automatic
constructor wiring to build a few structs, but then extract those structs
for further testing.
Example ?
package main
import (
"context"
"fmt"
"go.uber.org/fx"
)
func main() {
// Some external module that provides a user name.
type Username string
UserModule := fx.Provide(func() Username { return "john" })
// We want to use Fx to wire up our constructors, but don't actually want to
// run the application - we just want to yank out the user name.
//
// This is common in unit tests, and is even easier with the fxtest
// package's RequireStart and RequireStop helpers.
var user Username
app := fx.New(
UserModule,
fx.NopLogger, // silence test output
fx.Populate(&user),
)
if err := app.Start(context.Background()); err != nil {
panic(err)
}
defer app.Stop(context.Background())
fmt.Println(user)
}
Output:
john
Share
Format
Run
func Provide ?
func Provide(constructors ...interface{}) Option
Provide registers any number of constructor functions, teaching the
application how to instantiate various types. The supplied constructor
function(s) may depend on other types available in the application, must
return one or more objects, and may return an error. For example:
// Constructs type *C, depends on *A and *B.
func(*A, *B) *C
// Constructs type *C, depends on *A and *B, and indicates failure by
// returning an error.
func(*A, *B) (*C, error)
// Constructs types *B and *C, depends on *A, and can fail.
func(*A) (*B, *C, error)
The order in which constructors are provided doesn't matter, and passing
multiple Provide options appends to the application's collection of
constructors. Constructors are called only if one or more of their returned
types are needed, and their results are cached for reuse (so instances of a
type are effectively singletons within an application). Taken together,
these properties make it perfectly reasonable to Provide a large number of
constructors even if only a fraction of them are used.
See the documentation of the In and Out types for advanced features,
including optional parameters and named instances.
See the documentation for Private for restricting access to constructors.
Constructor functions should perform as little external interaction as
possible, and should avoid spawning goroutines. Things like server listen
loops, background timer loops, and background processing goroutines should
instead be managed using Lifecycle callbacks.
func RecoverFromPanics ?
added in
v1.19.0
func RecoverFromPanics() Option
RecoverFromPanics causes panics that occur in functions given to Provide,
Decorate, and Invoke to be recovered from.
This error can be retrieved as any other error, by using (*App).Err().
func Replace ?
added in
v1.17.0
func Replace(values ...interface{}) Option
Replace Caveats
Replace provides instantiated values for graph modification as if
they had been provided using a decorator with fx.Decorate.
The most specific type of each value (as determined by reflection) is used.
Refer to the documentation on fx.Decorate to see how graph modifications
work with fx.Module.
This serves a purpose similar to what fx.Supply does for fx.Provide.
For example, given,
var log *zap.Logger = ...
The following two forms are equivalent.
fx.Replace(log)
fx.Decorate(
func() *zap.Logger {
return log
},
)
Replace panics if a value (or annotation target) is an untyped nil or an error.
Replace Caveats ?As mentioned above, Replace uses the most specific type of the provided
value. For interface values, this refers to the type of the implementation,
not the interface. So if you try to replace an io.Writer, fx.Replace will
use the type of the implementation.
var stderr io.Writer = os.Stderr
fx.Replace(stderr)
Is equivalent to,
fx.Decorate(func() *os.File { return os.Stderr })
This is typically NOT what you intended. To replace the io.Writer in the
container with the value above, we need to use the fx.Annotate function with
the fx.As annotation.
fx.Replace(
fx.Annotate(os.Stderr, fx.As(new(io.Writer)))
)
func StartTimeout ?
added in
v1.5.0
func StartTimeout(v time.Duration) Option
StartTimeout changes the application's start timeout.
func StopTimeout ?
added in
v1.5.0
func StopTimeout(v time.Duration) Option
StopTimeout changes the application's stop timeout.
func Supply ?
added in
v1.12.0
func Supply(values ...interface{}) Option
Supply Caveats
Supply provides instantiated values for dependency injection as if
they had been provided using a constructor that simply returns them.
The most specific type of each value (as determined by reflection) is used.
This serves a purpose similar to what fx.Replace does for fx.Decorate.
For example, given:
type (
TypeA struct{}
TypeB struct{}
TypeC struct{}
)
var a, b, c = &TypeA{}, TypeB{}, &TypeC{}
The following two forms are equivalent:
fx.Supply(a, b, fx.Annotated{Target: c})
fx.Provide(
func() *TypeA { return a },
func() TypeB { return b },
fx.Annotated{Target: func() *TypeC { return c }},
)
Supply panics if a value (or annotation target) is an untyped nil or an error.
Supply Caveats ?As mentioned above, Supply uses the most specific type of the provided
value. For interface values, this refers to the type of the implementation,
not the interface. So if you supply an http.Handler, fx.Supply will use the
type of the implementation.
var handler http.Handler = http.HandlerFunc(f)
fx.Supply(handler)
Is equivalent to,
fx.Provide(func() http.HandlerFunc { return f })
This is typically NOT what you intended. To supply the handler above as an
http.Handler, we need to use the fx.Annotate function with the fx.As
annotation.
fx.Supply(
fx.Annotate(handler, fx.As(new(http.Handler))),
)
func WithLogger ?
added in
v1.14.0
func WithLogger(constructor interface{}) Option
WithLogger specifies how Fx should build an fxevent.Logger to log its events
to. The argument must be a constructor with one of the following return
types.
fxevent.Logger
(fxevent.Logger, error)
For example,
WithLogger(func(logger *zap.Logger) fxevent.Logger {
return &fxevent.ZapLogger{Logger: logger}
})
type Out ?
type Out = dig.Out
Result Structs
Named Values
Value Groups
Out is the inverse of In: it can be embedded in result structs to take
advantage of advanced features.
Modules should return a single result struct that embeds an Out in order to
provide a forward-compatible API: since adding fields to a struct is
backward-compatible, minor releases can provide additional types.
Result Structs ?Result structs are the inverse of parameter structs (discussed in the In
documentation). These structs represent multiple outputs from a
single function as fields. Fx treats all structs embedding fx.Out as result
structs, so other constructors can rely on the result struct's fields
directly.
Without result structs, we sometimes have function definitions like this:
func SetupGateways(conn *sql.DB) (*UserGateway, *CommentGateway, *PostGateway, error) {
// ...
}
With result structs, we can make this both more readable and easier to
modify in the future:
type Gateways struct {
fx.Out
Users *UserGateway
Comments *CommentGateway
Posts *PostGateway
}
func SetupGateways(conn *sql.DB) (Gateways, error) {
// ...
}
Named Values ?Some use cases require the application container to hold multiple values of
the same type. For details on consuming named values, see the documentation
for the In type.
A constructor that produces a result struct can tag any field with
`name:".."` to have the corresponding value added to the graph under the
specified name. An application may contain at most one unnamed value of a
given type, but may contain any number of named values of the same type.
type ConnectionResult struct {
fx.Out
ReadWrite *sql.DB `name:"rw"`
ReadOnly *sql.DB `name:"ro"`
}
func ConnectToDatabase(...) (ConnectionResult, error) {
// ...
return ConnectionResult{ReadWrite: rw, ReadOnly: ro}, nil
}
Value Groups ?To make it easier to produce and consume many values of the same type, Fx
supports named, unordered collections called value groups. For details on
consuming value groups, see the documentation for the In type.
Constructors can send values into value groups by returning a result struct
tagged with `group:".."`.
type HandlerResult struct {
fx.Out
Handler Handler `group:"server"`
}
func NewHelloHandler() HandlerResult {
// ...
}
func NewEchoHandler() HandlerResult {
// ...
}
Any number of constructors may provide values to this named collection, but
the ordering of the final collection is unspecified. Keep in mind that
value groups require parameter and result structs to use fields with
different types: if a group of constructors each returns type T, parameter
structs consuming the group must use a field of type []T.
To provide multiple values for a group from a result struct, produce a
slice and use the `,flatten` option on the group tag. This indicates that
each element in the slice should be injected into the group individually.
type IntResult struct {
fx.Out
Handler []int `group:"server"` // Consume as [][]int
Handler []int `group:"server,flatten"` // Consume as []int
}
type Printer ?
type Printer interface {
Printf(string, ...interface{})
}
Printer is the interface required by Fx's logging backend. It's implemented
by most loggers, including the one bundled with the standard library.
Note, this will be deprecate with next release and you will need to implement
fxevent.Logger interface instead.
type ShutdownOption ?
added in
v1.9.0
type ShutdownOption interface {
// contains filtered or unexported methods
}
ShutdownOption provides a way to configure properties of the shutdown
process. Currently, no options have been implemented.
func ExitCode ?
added in
v1.19.0
func ExitCode(code int) ShutdownOption
ExitCode is a ShutdownOption that may be passed to the Shutdown method of the
Shutdowner interface.
The given integer exit code will be broadcasted to any receiver waiting
on a ShutdownSignal from the [Wait] method.
func ShutdownTimeout
deprecated
added in
v1.19.0
func ShutdownTimeout(timeout time.Duration) ShutdownOption
ShutdownTimeout is a ShutdownOption that allows users to specify a timeout
for a given call to Shutdown method of the Shutdowner interface. As the
Shutdown method will block while waiting for a signal receiver relay
goroutine to stop.
Deprecated: This option has no effect. Shutdown is not a blocking operation.
type ShutdownSignal ?
added in
v1.19.0
type ShutdownSignal struct {
Signal os.Signal
ExitCode int
}
ShutdownSignal represents a signal to be written to Wait or Done.
Should a user call the Shutdown method via the Shutdowner interface with
a provided ExitCode, that exit code will be populated in the ExitCode field.
Should the application receive an operating system signal,
the Signal field will be populated with the received os.Signal.
func (ShutdownSignal) String ?
added in
v1.19.0
func (sig ShutdownSignal) String() string
String will render a ShutdownSignal type as a string suitable for printing.
type Shutdowner ?
added in
v1.9.0
type Shutdowner interface {
Shutdown(...ShutdownOption) error
}
Shutdowner provides a method that can manually trigger the shutdown of the
application by sending a signal to all open Done channels. Shutdowner works
on applications using Run as well as Start, Done, and Stop. The Shutdowner is
provided to all Fx applications.
Source Files
?
View all Source files
annotated.go
app.go
app_unixes.go
decorate.go
doc.go
extract.go
inout.go
invoke.go
lifecycle.go
log.go
module.go
populate.go
printer_writer.go
provide.go
replace.go
shutdown.go
signal.go
supply.go
version.go
Directories
?
Show internal
Expand all
Path
Synopsis
docs
module
fxevent
fxtest
internal
fxclock
fxlog
fxlog/foovendor
fxlog/sample.git
fxreflect
lifecycle
testutil
e2e
Module
tools
module
Click to show internal directories.
Click to hide internal directories.
Why Go
Use Cases
Case Studies
Get Started
Playground
Tour
Stack Overflow
Help
Packages
Standard Library
Sub-repositories
About Go Packages
About
Download
Blog
Issue Tracker
Release Notes
Brand Guidelines
Code of Conduct
Connect
GitHub
Slack
r/golang
Meetup
Golang Weekly
Copyright
Terms of Service
Privacy Policy
Report an Issue
Theme Toggle
Shortcuts Modal
Jump to
Close
Keyboard shortcuts
? : This menu
/ : Search site
f or F : Jump to
y or Y
: Canonical URL
Close
go.dev uses cookies from Google to deliver and enhance the quality of its services and to
analyze traffic. Learn more.
Okay
golang使用Fx實(shí)現(xiàn)自動(dòng)依賴(lài)注入| 青訓(xùn)營(yíng)筆記 - 掘金
golang使用Fx實(shí)現(xiàn)自動(dòng)依賴(lài)注入| 青訓(xùn)營(yíng)筆記 - 掘金
首頁(yè) 首頁(yè)
沸點(diǎn)
課程
直播
活動(dòng)
競(jìng)賽
商城
APP
插件 搜索歷史
清空
創(chuàng)作者中心
寫(xiě)文章 發(fā)沸點(diǎn) 寫(xiě)筆記 寫(xiě)代碼 草稿箱 創(chuàng)作靈感
查看更多
會(huì)員
登錄
注冊(cè)
golang使用Fx實(shí)現(xiàn)自動(dòng)依賴(lài)注入| 青訓(xùn)營(yíng)筆記
悠影
2022-06-12
754
這是我參與「第三屆青訓(xùn)營(yíng) -后端場(chǎng)」筆記創(chuàng)作活動(dòng)的第3篇筆記。
關(guān)于依賴(lài)注入是什么不想廢話(huà),java里面spring就是大量使用了依賴(lài)注入,想要理解依賴(lài)注入的思想直接看其他的文章就行,這里直接拋干貨。
例子
首先我們?cè)趌ogger.New()里面構(gòu)造一個(gè)zap.Logger對(duì)象。
var (
logger *zap.Logger
once sync.Once
)
// 創(chuàng)建新的logger
func New(cfg *config.Config) *zap.Logger {
once.Do(func() {
logger, _ = zap.NewProduction()
})
return logger
}
我們還需要一個(gè)S3ObjectAPI的Interface,他提供PutObject和PresignGetObject的方法。我們有一個(gè)s3.New()的構(gòu)造函數(shù),需要我們提供config和logger兩個(gè)對(duì)象進(jìn)行構(gòu)造,返回一個(gè)Mys3對(duì)象并有對(duì)應(yīng)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層也有一個(gè)service.New()的構(gòu)造函數(shù),他依賴(lài)我們上面構(gòu)造的Logger對(duì)象和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
)
// 啟動(dòng)一個(gè)新的service實(shí)例,當(dāng)然是單例模式
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
}
這樣的對(duì)象需要在main.go里面進(jìn)行手動(dòng)注入。你需要自己手動(dòng)處理好他們的依賴(lài)關(guān)系和構(gòu)建對(duì)象的順序。
// 這不用依賴(lài)注入框架真的好嗎。。。
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)
}
對(duì)于青訓(xùn)營(yíng)的demo版本的抖音項(xiàng)目來(lái)說(shuō),這個(gè)依賴(lài)注入的數(shù)量已經(jīng)比較復(fù)雜了。進(jìn)入真正的商業(yè)化項(xiàng)目的開(kāi)發(fā)后,依賴(lài)的數(shù)量只會(huì)比這個(gè)更多,屆時(shí)管理各個(gè)對(duì)象之間的依賴(lài)關(guān)系會(huì)極大的加重開(kāi)發(fā)者的心理負(fù)擔(dān)。我們的確需要一個(gè)工具來(lái)幫我們完成依賴(lài)注入的任務(wù)。
什么是Fx?
Fx是一個(gè)Golang下的依賴(lài)注入框架,他降低了在Golang中使用依賴(lài)注入的難度。
Fx可以和上面的構(gòu)造函數(shù)一起使用而無(wú)需嵌入特殊的類(lèi)型和使用特殊的struct,所以Fx可以輕松的使用在大多數(shù)的Go packages中。實(shí)際上在青訓(xùn)營(yíng)項(xiàng)目中,我們也是在后期才引入Fx的。Fx幫我們管理好了對(duì)象間的關(guān)系,避免了我們?cè)趍ain.go寫(xiě)出臃腫的注入代碼。
使用了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()
}
我們把上述的對(duì)象的構(gòu)造函數(shù)全部喂給fx.Provice(),然后把最終的http實(shí)例直接扔到fx.Invoke()里面,獲得一個(gè)fx.app,執(zhí)行這個(gè)app的Run方法,應(yīng)用就跑起來(lái)了,實(shí)際的效果和上述的main.go的效果是一樣的。
參考資料
uber-go/fx: A dependency injection based application framework for Go. (github.com)
fx package - go.uber.org/fx - Go Packages
悠影
學(xué)生
5
文章
1.7k
閱讀
5
粉絲 目錄 收起
例子
什么是Fx?
使用了Fx以后的代碼
參考資料
相關(guān)推薦 如果失業(yè)了,我們還能干啥? 35k閱讀 ?·? 419點(diǎn)贊語(yǔ)雀停機(jī)事件后,你也在找替代方案嗎? 16k閱讀 ?·? 74點(diǎn)贊博客搬家 | 2023年的最后一個(gè)月,宜在掘金開(kāi)啟寫(xiě)作之旅! 2.9k閱讀 ?·? 4點(diǎn)贊程序員自由創(chuàng)業(yè)周記#20:需求從何而來(lái) 1.9k閱讀 ?·? 4點(diǎn)贊28.7k Star! 突破邊界、強(qiáng)大易用建站利器 -- Halo 14k閱讀 ?·? 152點(diǎn)贊
Introduction | Fx
Introduction | Fx
Fx
Guide
API Reference
(opens new window)
GitHub
(opens new window)
Guide
API Reference
(opens new window)
GitHub
(opens new window) Get Started Create a minimal applicationAdd an HTTP serverRegister a handlerAdd a loggerDecouple registrationRegister another handlerRegister many handlersConclusionIntroductionConcepts Features FAQCommunity Release notes # Introduction Fx is a dependency injection system for Go.
With Fx you can: reduce boilerplate in setting up your application eliminate global state in your application add new components and have them instantly accessible across the application build general purpose shareable modules that just work If this is your first time with Fx,
check out our getting started tutorial. Edit this page (opens new window) Last Updated: 2/20/2024, 4:15:55 PM
←
Conclusion
Container
→
fx package - github.com/uber-go/fx - Go Packages
fx package - github.com/uber-go/fx - Go Packages
Skip to Main Content
Why Go
Case Studies
Common problems companies solve with Go
Use Cases
Stories about how and why companies use Go
Security Policy
How Go can help keep you secure by default
Learn
Docs
Effective Go
Tips for writing clear, performant, and idiomatic Go code
Go User Manual
A complete introduction to building software with Go
Standard library
Reference documentation for Go's standard library
Release Notes
Learn what's new in each Go release
Packages
Community
Recorded Talks
Videos from prior events
Meetups
Meet other local Go developers
Conferences
Learn and network with Go developers from around the world
Go blog
The Go project's official blog.
Go project
Get help and stay informed from Go
Get connected
Why Go
Why Go
Case Studies
Use Cases
Security Policy
Learn
Docs
Docs
Effective Go
Go User Manual
Standard library
Release Notes
Packages
Community
Community
Recorded Talks
Meetups
Conferences
Go blog
Go project
Get connected
Discover Packages
github.com/uber-go/fx
fx
package
module
Version:
v1.9.0
Opens a new window with list of versions in this module.
Latest
Latest
This package is not in the latest version of its module.
Go to latest
Published: Jan 22, 2019
License: MIT
Opens a new window with license information.
Imports: 18
Opens a new window with list of imports.
Imported by: 0
Opens a new window with list of known importers.
Main
Versions
Licenses
Imports
Imported By
Details
Valid go.mod file
The Go module system was introduced in Go 1.11 and is the official dependency management
solution for Go.
Redistributable license
Redistributable licenses place minimal restrictions on how software can be used,
modified, and redistributed.
Tagged version
Modules with tagged versions give importers more predictable builds.
Stable version
When a project reaches major version v1 it is considered stable.
Learn more about best practices
Repository
github.com/uber-go/fx
Links
Open Source Insights
Jump to ...
README
Installation
Stability
Documentation
Overview
Index
Examples
Constants
Variables
Functions
VisualizeError(err)
Types
type Annotated
type App
New(opts)
(app) Done()
(app) Err()
(app) Run()
(app) Start(ctx)
(app) StartTimeout()
(app) Stop(ctx)
(app) StopTimeout()
type DotGraph
type ErrorHandler
type Hook
type In
type Lifecycle
type Option
Error(errs)
ErrorHook(funcs)
Extract(target)
Invoke(funcs)
Logger(p)
Options(opts)
Populate(targets)
Provide(constructors)
StartTimeout(v)
StopTimeout(v)
type Out
type Printer
type ShutdownOption
type Shutdowner
Source Files
Directories
README
README
?
Fx
An application framework for Go that:
Makes dependency injection easy.
Eliminates the need for global state and func init().
Installation
We recommend locking to SemVer range ^1 using Glide:
glide get 'go.uber.org/fx#^1'
Stability
This library is v1 and follows SemVer strictly.
No breaking changes will be made to exported APIs before v2.0.0.
This project follows the Go Release Policy. Each major
version of Go is supported until there are two newer major releases.
Expand ?
Collapse ?
Documentation
?
Overview ?
Testing Fx Applications
Package fx is a framework that makes it easy to build applications out of
reusable, composable modules.
Fx applications use dependency injection to eliminate globals without the
tedium of manually wiring together function calls. Unlike other approaches
to dependency injection, Fx works with plain Go functions: you don't need
to use struct tags or embed special types, so Fx automatically works well
with most Go packages.
Basic usage is explained in the package-level example below. If you're new
to Fx, start there! Advanced features, including named instances, optional
parameters, and value groups, are explained under the In and Out types.
Testing Fx Applications ?To test functions that use the Lifecycle type or to write end-to-end tests
of your Fx application, use the helper functions and types provided by the
go.uber.org/fx/fxtest package.
Example ?
package main
import (
"context"
"log"
"net/http"
"os"
"time"
"go.uber.org/fx"
)
// NewLogger constructs a logger. It's just a regular Go function, without any
// special relationship to Fx.
//
// Since it returns a *log.Logger, Fx will treat NewLogger as the constructor
// function for the standard library's logger. (We'll see how to integrate
// NewLogger into an Fx application in the main function.) Since NewLogger
// doesn't have any parameters, Fx will infer that loggers don't depend on any
// other types - we can create them from thin air.
//
// Fx calls constructors lazily, so NewLogger will only be called only if some
// other function needs a logger. Once instantiated, the logger is cached and
// reused - within the application, it's effectively a singleton.
//
// By default, Fx applications only allow one constructor for each type. See
// the documentation of the In and Out types for ways around this restriction.
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
// NewHandler constructs a simple HTTP handler. Since it returns an
// http.Handler, Fx will treat NewHandler as the constructor for the
// http.Handler type.
//
// Like many Go functions, NewHandler also returns an error. If the error is
// non-nil, Go convention tells the caller to assume that NewHandler failed
// and the other returned values aren't safe to use. Fx understands this
// idiom, and assumes that any function whose last return value is an error
// follows this convention.
//
// Unlike NewLogger, NewHandler has formal parameters. Fx will interpret these
// parameters as dependencies: in order to construct an HTTP handler,
// NewHandler needs a logger. If the application has access to a *log.Logger
// constructor (like NewLogger above), it will use that constructor or its
// cached output and supply a logger to NewHandler. If the application doesn't
// know how to construct a logger and needs an HTTP handler, it will fail to
// start.
//
// Functions may also return multiple objects. For example, we could combine
// NewHandler and NewLogger into a single function:
//
// func NewHandlerAndLogger() (*log.Logger, http.Handler, error)
//
// Fx also understands this idiom, and would treat NewHandlerAndLogger as the
// constructor for both the *log.Logger and http.Handler types. Just like
// constructors for a single type, NewHandlerAndLogger would be called at most
// once, and both the handler and the logger would be cached and reused as
// necessary.
func NewHandler(logger *log.Logger) (http.Handler, error) {
logger.Print("Executing NewHandler.")
return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
logger.Print("Got a request.")
}), nil
}
// NewMux constructs an HTTP mux. Like NewHandler, it depends on *log.Logger.
// However, it also depends on the Fx-specific Lifecycle interface.
//
// A Lifecycle is available in every Fx application. It lets objects hook into
// the application's start and stop phases. In a non-Fx application, the main
// function often includes blocks like this:
//
// srv, err := NewServer() // some long-running network server
// if err != nil {
// log.Fatalf("failed to construct server: %v", err)
// }
// // Construct other objects as necessary.
// go srv.Start()
// defer srv.Stop()
//
// In this example, the programmer explicitly constructs a bunch of objects,
// crashing the program if any of the constructors encounter unrecoverable
// errors. Once all the objects are constructed, we start any background
// goroutines and defer cleanup functions.
//
// Fx removes the manual object construction with dependency injection. It
// replaces the inline goroutine spawning and deferred cleanups with the
// Lifecycle type.
//
// Here, NewMux makes an HTTP mux available to other functions. Since
// constructors are called lazily, we know that NewMux won't be called unless
// some other function wants to register a handler. This makes it easy to use
// Fx's Lifecycle to start an HTTP server only if we have handlers registered.
func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
logger.Print("Executing NewMux.")
// First, we construct the mux and server. We don't want to start the server
// until all handlers are registered.
mux := http.NewServeMux()
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// If NewMux is called, we know that another function is using the mux. In
// that case, we'll use the Lifecycle type to register a Hook that starts
// and stops our HTTP server.
//
// Hooks are executed in dependency order. At startup, NewLogger's hooks run
// before NewMux's. On shutdown, the order is reversed.
//
// Returning an error from OnStart hooks interrupts application startup. Fx
// immediately runs the OnStop portions of any successfully-executed OnStart
// hooks (so that types which started cleanly can also shut down cleanly),
// then exits.
//
// Returning an error from OnStop hooks logs a warning, but Fx continues to
// run the remaining hooks.
lc.Append(fx.Hook{
// To mitigate the impact of deadlocks in application startup and
// shutdown, Fx imposes a time limit on OnStart and OnStop hooks. By
// default, hooks have a total of 30 seconds to complete. Timeouts are
// passed via Go's usual context.Context.
OnStart: func(context.Context) error {
logger.Print("Starting HTTP server.")
// In production, we'd want to separate the Listen and Serve phases for
// better error-handling.
go server.ListenAndServe()
return nil
},
OnStop: func(ctx context.Context) error {
logger.Print("Stopping HTTP server.")
return server.Shutdown(ctx)
},
})
return mux
}
// Register mounts our HTTP handler on the mux.
//
// Register is a typical top-level application function: it takes a generic
// type like ServeMux, which typically comes from a third-party library, and
// introduces it to a type that contains our application logic. In this case,
// that introduction consists of registering an HTTP handler. Other typical
// examples include registering RPC procedures and starting queue consumers.
//
// Fx calls these functions invocations, and they're treated differently from
// the constructor functions above. Their arguments are still supplied via
// dependency injection and they may still return an error to indicate
// failure, but any other return values are ignored.
//
// Unlike constructors, invocations are called eagerly. See the main function
// below for details.
func Register(mux *http.ServeMux, h http.Handler) {
mux.Handle("/", h)
}
func main() {
app := fx.New(
// Provide all the constructors we need, which teaches Fx how we'd like to
// construct the *log.Logger, http.Handler, and *http.ServeMux types.
// Remember that constructors are called lazily, so this block doesn't do
// much on its own.
fx.Provide(
NewLogger,
NewHandler,
NewMux,
),
// Since constructors are called lazily, we need some invocations to
// kick-start our application. In this case, we'll use Register. Since it
// depends on an http.Handler and *http.ServeMux, calling it requires Fx
// to build those types using the constructors above. Since we call
// NewMux, we also register Lifecycle hooks to start and stop an HTTP
// server.
fx.Invoke(Register),
)
// In a typical application, we could just use app.Run() here. Since we
// don't want this example to run forever, we'll use the more-explicit Start
// and Stop.
startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil {
log.Fatal(err)
}
// Normally, we'd block here with <-app.Done(). Instead, we'll make an HTTP
// request to demonstrate that our server is running.
http.Get("http://localhost:8080/")
stopCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Stop(stopCtx); err != nil {
log.Fatal(err)
}
}
Output:
Executing NewLogger.
Executing NewMux.
Executing NewHandler.
Starting HTTP server.
Got a request.
Stopping HTTP server.
Share
Format
Run
Index ?
Constants
Variables
func VisualizeError(err error) (string, error)
type Annotated
type App
func New(opts ...Option) *App
func (app *App) Done() <-chan os.Signal
func (app *App) Err() error
func (app *App) Run()
func (app *App) Start(ctx context.Context) error
func (app *App) StartTimeout() time.Duration
func (app *App) Stop(ctx context.Context) error
func (app *App) StopTimeout() time.Duration
type DotGraph
type ErrorHandler
type Hook
type In
type Lifecycle
type Option
func Error(errs ...error) Option
func ErrorHook(funcs ...ErrorHandler) Option
func Extract(target interface{}) Option
func Invoke(funcs ...interface{}) Option
func Logger(p Printer) Option
func Options(opts ...Option) Option
func Populate(targets ...interface{}) Option
func Provide(constructors ...interface{}) Option
func StartTimeout(v time.Duration) Option
func StopTimeout(v time.Duration) Option
type Out
type Printer
type ShutdownOption
type Shutdowner
Examples ?
Package
Error
Populate
Constants ?
View Source
const DefaultTimeout = 15 * time.Second
DefaultTimeout is the default timeout for starting or stopping an
application. It can be configured with the StartTimeout and StopTimeout
options.
View Source
const Version = "1.9.0"
Version is exported for runtime compatibility checks.
Variables ?
View Source
var NopLogger = Logger(nopLogger{})
NopLogger disables the application's log output. Note that this makes some
failures difficult to debug, since no errors are printed to console.
Functions ?
func VisualizeError ?
added in
v1.7.0
func VisualizeError(err error) (string, error)
VisualizeError returns the visualization of the error if available.
Types ?
type Annotated ?
added in
v1.9.0
type Annotated struct {
// If specified, this will be used as the name for all non-error values returned
// by the constructor. For more information on named values, see the documentation
// for the fx.Out type.
//
// A name option may not be provided if a group option is provided.
Name string
// If specified, this will be used as the group name for all non-error values returned
// by the constructor. For more information on value groups, see the package documentation.
//
// A group option may not be provided if a name option is provided.
Group string
// Target is the constructor being annotated with fx.Annotated.
Target interface{}
}
Annotated annotates a constructor provided to Fx with additional options.
For example,
func NewReadOnlyConnection(...) (*Connection, error)
fx.Provide(fx.Annotated{
Name: "ro",
Target: NewReadOnlyConnection,
})
Is equivalent to,
type result struct {
fx.Out
Connection *Connection `name:"ro"`
}
fx.Provide(func(...) (Result, error) {
conn, err := NewReadOnlyConnection(...)
return Result{Connection: conn}, err
})
Annotated cannot be used with constructors which produce fx.Out objects.
type App ?
type App struct {
// contains filtered or unexported fields
}
An App is a modular application built around dependency injection. Most
users will only need to use the New constructor and the all-in-one Run
convenience method. In more unusual cases, users may need to use the Err,
Start, Done, and Stop methods by hand instead of relying on Run.
New creates and initializes an App. All applications begin with a
constructor for the Lifecycle type already registered.
In addition to that built-in functionality, users typically pass a handful
of Provide options and one or more Invoke options. The Provide options
teach the application how to instantiate a variety of types, and the Invoke
options describe how to initialize the application.
When created, the application immediately executes all the functions passed
via Invoke options. To supply these functions with the parameters they
need, the application looks for constructors that return the appropriate
types; if constructors for any required types are missing or any
invocations return an error, the application will fail to start (and Err
will return a descriptive error message).
Once all the invocations (and any required constructors) have been called,
New returns and the application is ready to be started using Run or Start.
On startup, it executes any OnStart hooks registered with its Lifecycle.
OnStart hooks are executed one at a time, in order, and must all complete
within a configurable deadline (by default, 15 seconds). For details on the
order in which OnStart hooks are executed, see the documentation for the
Start method.
At this point, the application has successfully started up. If started via
Run, it will continue operating until it receives a shutdown signal from
Done (see the Done documentation for details); if started explicitly via
Start, it will operate until the user calls Stop. On shutdown, OnStop hooks
execute one at a time, in reverse order, and must all complete within a
configurable deadline (again, 15 seconds by default).
func New ?
func New(opts ...Option) *App
New creates and initializes an App, immediately executing any functions
registered via Invoke options. See the documentation of the App struct for
details on the application's initialization, startup, and shutdown logic.
func (*App) Done ?
func (app *App) Done() <-chan os.Signal
Done returns a channel of signals to block on after starting the
application. Applications listen for the SIGINT and SIGTERM signals; during
development, users can send the application SIGTERM by pressing Ctrl-C in
the same terminal as the running process.
Alternatively, a signal can be broadcast to all done channels manually by
using the Shutdown functionality (see the Shutdowner documentation for details).
func (*App) Err ?
func (app *App) Err() error
Err returns any error encountered during New's initialization. See the
documentation of the New method for details, but typical errors include
missing constructors, circular dependencies, constructor errors, and
invocation errors.
Most users won't need to use this method, since both Run and Start
short-circuit if initialization failed.
func (*App) Run ?
func (app *App) Run()
Run starts the application, blocks on the signals channel, and then
gracefully shuts the application down. It uses DefaultTimeout to set a
deadline for application startup and shutdown, unless the user has
configured different timeouts with the StartTimeout or StopTimeout options.
It's designed to make typical applications simple to run.
However, all of Run's functionality is implemented in terms of the exported
Start, Done, and Stop methods. Applications with more specialized needs
can use those methods directly instead of relying on Run.
func (*App) Start ?
func (app *App) Start(ctx context.Context) error
Start kicks off all long-running goroutines, like network servers or
message queue consumers. It does this by interacting with the application's
Lifecycle.
By taking a dependency on the Lifecycle type, some of the user-supplied
functions called during initialization may have registered start and stop
hooks. Because initialization calls constructors serially and in dependency
order, hooks are naturally registered in dependency order too.
Start executes all OnStart hooks registered with the application's
Lifecycle, one at a time and in order. This ensures that each constructor's
start hooks aren't executed until all its dependencies' start hooks
complete. If any of the start hooks return an error, Start short-circuits,
calls Stop, and returns the inciting error.
Note that Start short-circuits immediately if the New constructor
encountered any errors in application initialization.
func (*App) StartTimeout ?
added in
v1.5.0
func (app *App) StartTimeout() time.Duration
StartTimeout returns the configured startup timeout. Apps default to using
DefaultTimeout, but users can configure this behavior using the
StartTimeout option.
func (*App) Stop ?
func (app *App) Stop(ctx context.Context) error
Stop gracefully stops the application. It executes any registered OnStop
hooks in reverse order, so that each constructor's stop hooks are called
before its dependencies' stop hooks.
If the application didn't start cleanly, only hooks whose OnStart phase was
called are executed. However, all those hooks are executed, even if some
fail.
func (*App) StopTimeout ?
added in
v1.5.0
func (app *App) StopTimeout() time.Duration
StopTimeout returns the configured shutdown timeout. Apps default to using
DefaultTimeout, but users can configure this behavior using the StopTimeout
option.
type DotGraph ?
added in
v1.7.0
type DotGraph string
DotGraph contains a DOT language visualization of the dependency graph in
an Fx application. It is provided in the container by default at
initialization. On failure to build the dependency graph, it is attached
to the error and if possible, colorized to highlight the root cause of the
failure.
type ErrorHandler ?
added in
v1.7.0
type ErrorHandler interface {
HandleError(error)
}
ErrorHandler handles Fx application startup errors.
type Hook ?
type Hook struct {
OnStart func(context.Context) error
OnStop func(context.Context) error
}
A Hook is a pair of start and stop callbacks, either of which can be nil.
If a Hook's OnStart callback isn't executed (because a previous OnStart
failure short-circuited application startup), its OnStop callback won't be
executed.
type In ?
type In struct{ dig.In }
Parameter Structs
Optional Dependencies
Named Values
Value Groups
In can be embedded in a constructor's parameter struct to take advantage of
advanced dependency injection features.
Modules should take a single parameter struct that embeds an In in order to
provide a forward-compatible API: since adding fields to a struct is
backward-compatible, modules can then add optional dependencies in minor
releases.
Parameter Structs ?Fx constructors declare their dependencies as function parameters. This can
quickly become unreadable if the constructor has a lot of dependencies.
func NewHandler(users *UserGateway, comments *CommentGateway, posts *PostGateway, votes *VoteGateway, authz *AuthZGateway) *Handler {
// ...
}
To improve the readability of constructors like this, create a struct that
lists all the dependencies as fields and change the function to accept that
struct instead. The new struct is called a parameter struct.
Fx has first class support for parameter structs: any struct embedding
fx.In gets treated as a parameter struct, so the individual fields in the
struct are supplied via dependency injection. Using a parameter struct, we
can make the constructor above much more readable:
type HandlerParams struct {
fx.In
Users *UserGateway
Comments *CommentGateway
Posts *PostGateway
Votes *VoteGateway
AuthZ *AuthZGateway
}
func NewHandler(p HandlerParams) *Handler {
// ...
}
Though it's rarely a good idea, constructors can receive any combination of
parameter structs and parameters.
func NewHandler(p HandlerParams, l *log.Logger) *Handler {
// ...
}
Optional Dependencies ?Constructors often have soft dependencies on some types: if those types are
missing, they can operate in a degraded state. Fx supports optional
dependencies via the `optional:"true"` tag to fields on parameter structs.
type UserGatewayParams struct {
fx.In
Conn *sql.DB
Cache *redis.Client `optional:"true"`
}
If an optional field isn't available in the container, the constructor
receives the field's zero value.
func NewUserGateway(p UserGatewayParams, log *log.Logger) (*UserGateway, error) {
if p.Cache != nil {
log.Print("Caching disabled")
}
// ...
}
Constructors that declare optional dependencies MUST gracefully handle
situations in which those dependencies are absent.
The optional tag also allows adding new dependencies without breaking
existing consumers of the constructor.
Named Values ?Some use cases require the application container to hold multiple values of
the same type. For details on producing named values, see the documentation
for the Out type.
Fx allows functions to consume named values via the `name:".."` tag on
parameter structs. Note that both the name AND type of the fields on the
parameter struct must match the corresponding result struct.
type GatewayParams struct {
fx.In
WriteToConn *sql.DB `name:"rw"`
ReadFromConn *sql.DB `name:"ro"`
}
The name tag may be combined with the optional tag to declare the
dependency optional.
type GatewayParams struct {
fx.In
WriteToConn *sql.DB `name:"rw"`
ReadFromConn *sql.DB `name:"ro" optional:"true"`
}
func NewCommentGateway(p GatewayParams, log *log.Logger) (*CommentGateway, error) {
if p.ReadFromConn == nil {
log.Print("Warning: Using RW connection for reads")
p.ReadFromConn = p.WriteToConn
}
// ...
}
Value Groups ?To make it easier to produce and consume many values of the same type, Fx
supports named, unordered collections called value groups. For details on
producing value groups, see the documentation for the Out type.
Functions can depend on a value group by requesting a slice tagged with
`group:".."`. This will execute all constructors that provide a value to
that group in an unspecified order, then collect all the results into a
single slice. Keep in mind that this makes the types of the parameter and
result struct fields different: if a group of constructors each returns
type T, parameter structs consuming the group must use a field of type []T.
type ServerParams struct {
fx.In
Handlers []Handler `group:"server"`
}
func NewServer(p ServerParams) *Server {
server := newServer()
for _, h := range p.Handlers {
server.Register(h)
}
return server
}
Note that values in a value group are unordered. Fx makes no guarantees
about the order in which these values will be produced.
type Lifecycle ?
type Lifecycle interface {
Append(Hook)
}
Lifecycle allows constructors to register callbacks that are executed on
application start and stop. See the documentation for App for details on Fx
applications' initialization, startup, and shutdown logic.
type Option ?
type Option interface {
// contains filtered or unexported methods
}
An Option configures an App using the functional options paradigm
popularized by Rob Pike. If you're unfamiliar with this style, see
https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html.
func Error ?
added in
v1.6.0
func Error(errs ...error) Option
Error registers any number of errors with the application to short-circuit
startup. If more than one error is given, the errors are combined into a
single error.
Similar to invocations, errors are applied in order. All Provide and Invoke
options registered before or after an Error option will not be applied.
Example ?
package main
import (
"errors"
"fmt"
"net/http"
"os"
"go.uber.org/fx"
)
func main() {
// A module that provides a HTTP server depends on
// the $PORT environment variable. If the variable
// is unset, the module returns an fx.Error option.
newHTTPServer := func() fx.Option {
port := os.Getenv("PORT")
if port == "" {
return fx.Error(errors.New("$PORT is not set"))
}
return fx.Provide(&http.Server{
Addr: fmt.Sprintf(":%s", port),
})
}
app := fx.New(
newHTTPServer(),
fx.Invoke(func(s *http.Server) error { return s.ListenAndServe() }),
)
fmt.Println(app.Err())
}
Output:
$PORT is not set
Share
Format
Run
func ErrorHook ?
added in
v1.7.0
func ErrorHook(funcs ...ErrorHandler) Option
ErrorHook registers error handlers that implement error handling functions.
They are executed on invoke failures. Passing multiple ErrorHandlers appends
the new handlers to the application's existing list.
func Extract ?
func Extract(target interface{}) Option
Extract fills the given struct with values from the dependency injection
container on application initialization. The target MUST be a pointer to a
struct. Only exported fields will be filled.
Extract will be deprecated soon: use Populate instead, which doesn't
require defining a container struct.
func Invoke ?
func Invoke(funcs ...interface{}) Option
Invoke registers functions that are executed eagerly on application start.
Arguments for these invocations are built using the constructors registered
by Provide. Passing multiple Invoke options appends the new invocations to
the application's existing list.
Unlike constructors, invocations are always executed, and they're always
run in order. Invocations may have any number of returned values. If the
final returned object is an error, it's assumed to be a success indicator.
All other returned values are discarded.
Typically, invoked functions take a handful of high-level objects (whose
constructors depend on lower-level objects) and introduce them to each
other. This kick-starts the application by forcing it to instantiate a
variety of types.
To see an invocation in use, read through the package-level example. For
advanced features, including optional parameters and named instances, see
the documentation of the In and Out types.
func Logger ?
func Logger(p Printer) Option
Logger redirects the application's log output to the provided printer.
func Options ?
func Options(opts ...Option) Option
Options converts a collection of Options into a single Option. This allows
packages to bundle sophisticated functionality into easy-to-use Fx modules.
For example, a logging package might export a simple option like this:
package logging
var Module = fx.Provide(func() *log.Logger {
return log.New(os.Stdout, "", 0)
})
A shared all-in-one microservice package could then use Options to bundle
logging with similar metrics, tracing, and gRPC modules:
package server
var Module = fx.Options(
logging.Module,
metrics.Module,
tracing.Module,
grpc.Module,
)
Since this all-in-one module has a minimal API surface, it's easy to add
new functionality to it without breaking existing users. Individual
applications can take advantage of all this functionality with only one
line of code:
app := fx.New(server.Module)
Use this pattern sparingly, since it limits the user's ability to customize
their application.
func Populate ?
added in
v1.4.0
func Populate(targets ...interface{}) Option
Populate sets targets with values from the dependency injection container
during application initialization. All targets must be pointers to the
values that must be populated. Pointers to structs that embed In are
supported, which can be used to populate multiple values in a struct.
This is most helpful in unit tests: it lets tests leverage Fx's automatic
constructor wiring to build a few structs, but then extract those structs
for further testing.
Example ?
package main
import (
"context"
"fmt"
"go.uber.org/fx"
)
func main() {
// Some external module that provides a user name.
type Username string
UserModule := fx.Provide(func() Username { return "john" })
// We want to use Fx to wire up our constructors, but don't actually want to
// run the application - we just want to yank out the user name.
//
// This is common in unit tests, and is even easier with the fxtest
// package's RequireStart and RequireStop helpers.
var user Username
app := fx.New(
UserModule,
fx.Populate(&user),
)
if err := app.Start(context.Background()); err != nil {
panic(err)
}
defer app.Stop(context.Background())
fmt.Println(user)
}
Output:
john
Share
Format
Run
func Provide ?
func Provide(constructors ...interface{}) Option
Provide registers any number of constructor functions, teaching the
application how to instantiate various types. The supplied constructor
function(s) may depend on other types available in the application, must
return one or more objects, and may return an error. For example:
// Constructs type *C, depends on *A and *B.
func(*A, *B) *C
// Constructs type *C, depends on *A and *B, and indicates failure by
// returning an error.
func(*A, *B) (*C, error)
// Constructs types *B and *C, depends on *A, and can fail.
func(*A) (*B, *C, error)
The order in which constructors are provided doesn't matter, and passing
multiple Provide options appends to the application's collection of
constructors. Constructors are called only if one or more of their returned
types are needed, and their results are cached for reuse (so instances of a
type are effectively singletons within an application). Taken together,
these properties make it perfectly reasonable to Provide a large number of
constructors even if only a fraction of them are used.
See the documentation of the In and Out types for advanced features,
including optional parameters and named instances.
func StartTimeout ?
added in
v1.5.0
func StartTimeout(v time.Duration) Option
StartTimeout changes the application's start timeout.
func StopTimeout ?
added in
v1.5.0
func StopTimeout(v time.Duration) Option
StopTimeout changes the application's stop timeout.
type Out ?
type Out struct{ dig.Out }
Result Structs
Named Values
Value Groups
Out is the inverse of In: it can be embedded in result structs to take
advantage of advanced features.
Modules should return a single result struct that embeds an Out in order to
provide a forward-compatible API: since adding fields to a struct is
backward-compatible, minor releases can provide additional types.
Result Structs ?Result structs are the inverse of parameter structs (discussed in the In
documentation). These structs represent multiple outputs from a
single function as fields. Fx treats all structs embedding fx.Out as result
structs, so other constructors can rely on the result struct's fields
directly.
Without result structs, we sometimes have function definitions like this:
func SetupGateways(conn *sql.DB) (*UserGateway, *CommentGateway, *PostGateway, error) {
// ...
}
With result structs, we can make this both more readable and easier to
modify in the future:
type Gateways struct {
fx.Out
Users *UserGateway
Comments *CommentGateway
Posts *PostGateway
}
func SetupGateways(conn *sql.DB) (Gateways, error) {
// ...
}
Named Values ?Some use cases require the application container to hold multiple values of
the same type. For details on consuming named values, see the documentation
for the In type.
A constructor that produces a result struct can tag any field with
`name:".."` to have the corresponding value added to the graph under the
specified name. An application may contain at most one unnamed value of a
given type, but may contain any number of named values of the same type.
type ConnectionResult struct {
fx.Out
ReadWrite *sql.DB `name:"rw"`
ReadOnly *sql.DB `name:"ro"`
}
func ConnectToDatabase(...) (ConnectionResult, error) {
// ...
return ConnectionResult{ReadWrite: rw, ReadOnly: ro}, nil
}
Value Groups ?To make it easier to produce and consume many values of the same type, Fx
supports named, unordered collections called value groups. For details on
consuming value groups, see the documentation for the In type.
Constructors can send values into value groups by returning a result struct
tagged with `group:".."`.
type HandlerResult struct {
fx.Out
Handler Handler `group:"server"`
}
func NewHelloHandler() HandlerResult {
// ...
}
func NewEchoHandler() HandlerResult {
// ...
}
Any number of constructors may provide values to this named collection, but
the ordering of the final collection is unspecified. Keep in mind that
value groups require parameter and result structs to use fields with
different types: if a group of constructors each returns type T, parameter
structs consuming the group must use a field of type []T.
type Printer ?
type Printer interface {
Printf(string, ...interface{})
}
Printer is the interface required by Fx's logging backend. It's implemented
by most loggers, including the one bundled with the standard library.
type ShutdownOption ?
added in
v1.9.0
type ShutdownOption interface {
// contains filtered or unexported methods
}
ShutdownOption provides a way to configure properties of the shutdown
process. Currently, no options have been implemented.
type Shutdowner ?
added in
v1.9.0
type Shutdowner interface {
Shutdown(...ShutdownOption) error
}
Shutdowner provides a method that can manually trigger the shutdown of the
application by sending a signal to all open Done channels. Shutdowner works
on applications using Run as well as Start, Done, and Stop. The Shutdowner is
provided to all Fx applications.
Source Files
?
View all Source files
annotated.go
app.go
doc.go
extract.go
inout.go
lifecycle.go
populate.go
shutdown.go
version.go
Directories
?
Show internal
Expand all
Path
Synopsis
fxtest
internal
fxlog
fxlog/foovendor
fxlog/sample.git
fxreflect
lifecycle
Click to show internal directories.
Click to hide internal directories.
Why Go
Use Cases
Case Studies
Get Started
Playground
Tour
Stack Overflow
Help
Packages
Standard Library
Sub-repositories
About Go Packages
About
Download
Blog
Issue Tracker
Release Notes
Brand Guidelines
Code of Conduct
Connect
GitHub
Slack
r/golang
Meetup
Golang Weekly
Copyright
Terms of Service
Privacy Policy
Report an Issue
Theme Toggle
Shortcuts Modal
Jump to
Close
Keyboard shortcuts
? : This menu
/ : Search site
f or F : Jump to
y or Y
: Canonical URL
Close
go.dev uses cookies from Google to deliver and enhance the quality of its services and to
analyze traffic. Learn more.
Okay
Get started with Fx | Fx
Get started with Fx | Fx
Fx
Guide
API Reference
(opens new window)
GitHub
(opens new window)
Guide
API Reference
(opens new window)
GitHub
(opens new window) Get Started Create a minimal applicationAdd an HTTP serverRegister a handlerAdd a loggerDecouple registrationRegister another handlerRegister many handlersConclusionIntroductionConcepts Features FAQCommunity Release notes # Get started with Fx This introduces you to the basics of Fx.
In this tutorial you will: start an empty application add an HTTP server to it register a handler with the server add logging to your application refactor to loosen coupling to your handler add another handler to the server generalize your implementation First, get set up for the rest of the tutorial. Start a new empty project. mkdir fxdemo
cd fxdemo
go mod init example.com/fxdemo
Install the latest version of Fx. go get go.uber.org/fx@latest
Now begin by creating a minimal application. Edit this page (opens new window) Last Updated: 2/20/2024, 4:15:55 PM