Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Factory like provider? #1099

Open
joesonw opened this issue Jul 4, 2023 · 3 comments
Open

Factory like provider? #1099

joesonw opened this issue Jul 4, 2023 · 3 comments

Comments

@joesonw
Copy link

joesonw commented Jul 4, 2023

Is your feature request related to a problem? Please describe.
current I have this kind of scenario, I have mutiple endpoints registers into fx and wil be mounted to a service

type Endpoint1 struct {
    fx.In
    Logger *zap.Logger // prefer to be automatically provided as logger.Named('Endpoint1')
}
type Endpoint2 struct {
    fx.In
    Logger *zap.Logger   // prefer to be automatically provided as logger.Named('Endpoint2')
}

But all the endpoints receives the same logger. I would like it to invoke some kind of factory method that is aware of what and where an dependency is injected, thus each logger can be named one.

Describe the solution you'd like
A clear and concise description of what you want to happen.

fx.ProvideFactory((*zap.Logger)(nil), func (logger *zap.Logger) {
  return func (contextInfo *fx.FactoryDenepdencyContext) fx.Option {
     in, ok := contextInfo.Target.(interface { GetPath() string })
     if ok {
       return logger.Named(in.GetPath())
     }
     return logger
  }
})

Describe alternatives you've considered
Currently, it's being managed by hard coding.

Is this a breaking change?
No, This will be an additional feature

Additional context
.

@alexisvisco
Copy link

Same concerns for us :)

@abhinav
Copy link
Collaborator

abhinav commented Aug 17, 2023

This is an interesting feature idea. I'll let the maintainers decide their route there, but until then, one thing you can do is this:

Namespace your endpoints into their own Fx modules.
That is, instead of fx.Provide(NewEndpoint1), do:

fx.Module("endpoint1",
  fx.Provide(NewEndpoint1),
)

Once they're in their own modules, you can decorate the logger they receive with fx.Decorate:

fx.Module("endpoint1",
  fx.Provide(..),
  fx.Decorate(func(log *zap.Logger) *zap.Logger {
    return log.With(zap.String("endpoint", "endpoint1"))
  }),
)

The fx.Module will limit the scope of that decoration to just that endpoint.
You can also create a helper to do this for you:

func EndpointLogger(name string) fx.Option {
  fx.Decorate(func(log *zap.Logger) *zap.Logger {
    return log.With(zap.String("endpoint", name))
  })
}

// or even

func ProvideEndpoint(name string, ctor interface{}) fx.Option {
  return fx.Module(name,
    fx.Provide(ctor),
    fx.Decorate(func(log *zap.Logger) *zap.Logger {
      return log.With(zap.String("endpoint", name))
    }),
  )
}

Hope this helps!

@abhinav
Copy link
Collaborator

abhinav commented Aug 17, 2023

Something with a smaller surface area that might be worth exploring here is some sort of module information struct passed to decorators. Strawman:

type ModuleInfo struct {
  Name string
  Location Location
  // other metadata about the module
}

If a decorator has a ModuleInfo dependency, it will be invoked with information about the module scope where it's being applied.

(This is just a strawman suggestion; not a real feature request.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

4 participants