uber-go / fx

A dependency injection based application framework for Go.
https://uber-go.github.io/fx/
MIT License
5.65k stars 287 forks source link

Support In/Out parameter packs #666

Open oakad opened 5 years ago

oakad commented 5 years ago

Sometimes, it is desirable to have a nested fx.App instance. For example, in applications with complex configuration, it makes sense to have an initial start-up sequence which will construct a configuration dependent start up sequence (a nested fx.App) and will execute that nested sequence as a dependency.

To forward the state information from outer to inner fx.App, something like an "In/Out" parameter pack will be exceptionally handy:

type ParamPack struct {
  fx.In
  fx.Out
  Fld0 Type0
  // etc ...
}

Unfortunately, it would not work with the current version of FX:

cannot depend on result objects: ParamPack embeds a dig.Out

This results in a necessity to use an unfortunate boilerplate, in order to forward the products of the outer fx.App instance to the nested one:

type ParamPackIn struct {
  fx.In
  Fld0 Type0
  // etc ...
}

type ParamPackOut struct {
  fx.Out
  Fld0 Type0
  // etc ...
}

func (pp ParamPackIn) Forward() ParamPackOut {
  return ParamPackOut {
   Fld0: pp.Fld0,
   // etc ...
  }
}

I hope this illustrates the utility of "In/Out" parameter packs.

glibsm commented 5 years ago

Hi @oakad, thanks for getting in touch.

It took me a little bit to digest this issue and understand what is the actual problem you are trying to solve. Configuration bootstrapping in particular is a tricky problem, we have encountered that from time to time but have been able to work around it.

Are there some more concrete examples that you can provide on what situations force you to pass objects from one application graph into the next, or is it only in configuration driven cases?

oakad commented 5 years ago

I'm sorry for being lazy and not providing the actual application example. Here it is, below.

As you can see, I'm trying to run an fx.App from within another fx.App. I need to pass some parameters from the outer app to the nested app via the makeNested constructor. It works pretty well, as long as number of parameters being passed is not too big and can be specified directly in the Go func declaration (pt struct type in the example). However, in the real app, the number of parameters are big and labels are requires, hence the need for in-out parameter structs: the "provide" of the outer fx.App passes an "in" parameter pack to the makeNested constructor, but the forwarding function inside the makeNested constructor proper is expected to return an "out" parameter pack, making the "pt" struct a good candidate for conversion into an "in/out" parameter pack.

package main

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

type pt struct {
    tag string
}

type nested *fx.App

func makeNested(lc fx.Lifecycle, param pt) nested {
    n := fx.New(
        fx.Provide(
            func() pt {
                return param
            },
        ),
        fx.Invoke(func (param pt) {
            fmt.Println(param)
        }),
    )
    lc.Append(fx.Hook {
        OnStart: n.Start,
        OnStop: n.Stop,
    })
    return n
}

func main() {
    fx.New(
        fx.Provide(
            func() pt {
                return pt{
                    tag: "Aaaa!",
                }
            },
            makeNested,
        ),
        fx.Invoke(func (n nested) {}),
    ).Run()
}