Just some notes as I am learning this… There aren’t good answers here, mostly questions (am I doing it right?). All these examples are a part of (not very well) organized GitHub repo here.
Structure injection
Having once hated the magic of Spring’s DI, I’ve grown cautiously accustomed to the whole @Autowired stuff. When it comes to Go, I’ve come across Uber’s Fx framework which looks great, but I haven’t been able to figure out just how to automagically inject fields whose values are being Provided into other structs.
An attempt to ask our overlords yielded something not very clear.
I finally broke down and asked a stupid question. Then I found the answer — do not use constructors, just use fx.In
in combination with fx.Populate()
. Finally this works. But doesn’t seem ideal in all cases…
Avoiding boilerplate duplication
This is all well and good, but not always. For example, consider this example in addition to the above:
package dependencies
import "go.uber.org/fx"
type Foo string
type Bar string
type Baz string
type DependenciesType struct {
fx.In
Foo Foo
Bar Bar
Baz Baz
}
func NewFoo() Foo {
return "foo"
}
func NewBar() Bar {
return "bar"
}
func NewBaz() Baz {
return "baz"
}
var Dependencies DependenciesType
var DependenciesModule = fx.Options(
fx.Provide(NewFoo),
fx.Provide(NewBar),
fx.Provide(NewBaz),
)
If I try to use it as dependencies.Dependencies
, it’s ok (as above). But if I rather want to get rid of this var
, and rather use constructors. But I don’t like the proliferation of parameters into constructors. I can use Parameter objects but I’d like to avoid the boilerplate of copying fields from the Parameter object into the struct
being returned, so I’d like to use reflection like so (generics are nice):
package utils
import "reflect"
func Construct[P any, T any, PT interface{ *T }](params interface{}) PT {
p := PT(new(T))
construct0(params, p)
return p
}
func construct0(params interface{}, retval interface{}) {
// Check if retval is a pointer
rv := reflect.ValueOf(retval)
if rv.Kind() != reflect.Ptr {
panic("retval is not a pointer")
}
// Dereference the pointer to get the underlying value
rv = rv.Elem()
// Check if the dereferenced value is a struct
if rv.Kind() != reflect.Struct {
panic("retval is not a pointer to a struct")
}
// Now, get the value of params
rp := reflect.ValueOf(params)
if rp.Kind() != reflect.Struct {
panic("params is not a struct")
}
// Iterate over the fields of params and copy to retval
for i := 0; i < rp.NumField(); i++ {
name := rp.Type().Field(i).Name
field, ok := rv.Type().FieldByName(name)
if ok && field.Type == rp.Field(i).Type() {
rv.FieldByName(name).Set(rp.Field(i))
}
}
}
So then I can use it as follows:
package dependencies
import (
"example.com/fxtest/utils"
"go.uber.org/fx"
)
type Foo *string
type Bar *string
type Baz *string
type DependenciesType struct {
Foo Foo
Bar Bar
Baz Baz
}
type DependenciesParams struct {
fx.In
Foo Foo
Bar Bar
Baz Baz
}
func NewFoo() Foo {
s := "foo"
return &s
}
func NewBar() Bar {
s := "bar"
return &s
}
func NewBaz() Baz {
s := "foo"
return &s
}
func NewDependencies(params DependenciesParams) *DependenciesType {
retval := utils.Construct[DependenciesParams, DependenciesType](params)
return retval
}
var DependenciesModule = fx.Module("dependencies",
fx.Provide(NewFoo),
fx.Provide(NewBar),
fx.Provide(NewBaz),
fx.Provide(NewDependencies),
)
But while this takes care of proliferating parameters in the constructor as well as the boilerplate step of copying, I still cannot avoid duplicating the fields between DependenciesType
and DependenciesParams
, running into various problems.
Looks like this is still TBD on the library side; I’ll see if I can get further.
Conditional Provide
When using constructors, I would have a construct such as:
type X struct {
field *FieldType
}
func NewX() *X {
x := &X{}
if os.Getenv("FOO") == "BAR" {
x.field = NewFieldType(...)
}
}
In other words, I wanted field
to only be initialized if some environment variable is set. In transitioning from using constructors to fx.Provide()
, I wanted to keep the same functionality, so I came up with this:
type XType struct {
fx.In
field *FieldType `optional:"true"`
}
var X XType
func NewX() *X {
x := &X{}
if os.Getenv("FOO") == "BAR" {
x.field = NewFieldType(...)
}
}
var XModule = fx.Module("x",
func() fx.Option {
if os.Getenv("FOO") == "BAR" {
return fx.Options(
fx.Provide(NewFieldType),
)
}
return fx.Options()
}(),
fx.Populate(&X),
Works fine. But is it the right way?