uber-go / dig

A reflection based dependency injection toolkit for Go.
https://go.uber.org/dig
MIT License
3.92k stars 206 forks source link

Add API to invoke named values #181

Open lianzhao opened 6 years ago

lianzhao commented 6 years ago

I'd like to do something like these

c.Provide(myExporter1Ctor, dig.Name("myExporter1"))
c.Provide(myExporter2Ctor, dig.Name("myExporter2"))
err := c.Invoke(func() *exporter.Exporter {
  // ...
}, dig.Name("myExporter1"))
JamesArthurHolland commented 5 years ago

Has this been implemented? Are there any docs on how to use this?

It would be good to be able to use the named invocation without the use of param structs.

glibsm commented 5 years ago

It was merged in https://github.com/uber-go/dig/pull/233, but 1.8.0 has not been released

JamesArthurHolland commented 5 years ago

cannot use dig.Name("myExporter1") (type dig.ProvideOption) as type dig.InvokeOption in argument to container.Invoke: dig.ProvideOption does not implement dig.InvokeOption (missing dig.unimplemented method)

glibsm commented 5 years ago

cannot use dig.Name("myExporter1") (type dig.ProvideOption) as type dig.InvokeOption in argument to container.Invoke: dig.ProvideOption does not implement dig.InvokeOption (missing dig.unimplemented method)

The types dig.ProvideOption, part of the container.Provide, and type dig.InvokeOption, part of the container.Invoke, are different. They are strongly typed to their respective method calls, so unfortunately the same dig.Name can't be used in both call sites.

There is no corresponding inlined Invoke option, meaning you have to use fx.In on the receiving end.

The rationale here is that dig.Name allows you to use existing (even stdlib) constructors, but name their return types if you need more than one to avoid a clash.

Whenever you're writing Invoke code, the thought here is that you are writing something new and not attempting to use an existing signature or a function (mostly since returns are completely discarded).

To put what I just said in code, lets assume you have two clashing constructors (in this example returning a string) which you want to use.

package main

import (
    "fmt"

    "go.uber.org/dig"
)

func username() string {
    return "foo"
}

func password() string {
    return "bar"
}

func main() {
    c := dig.New()
    c.Provide(username, dig.Name("username"))
    c.Provide(password, dig.Name("password"))

    err := c.Invoke(func(p struct {
        dig.In

        U string `name:"username"`
        P string `name:"password"`
    }) {
        fmt.Println("user >>>", p.U)
        fmt.Println("pwd  >>>", p.P)
    })
    if err != nil {
        panic(err)
    }
}

If you have ideas of a shorthand InvokeOption that will mirror the dig.Name, PRs are always welcome :)

Perhaps dig.Named can be an option. You provide something with a name, and you invoked a named something. 🤷‍♂

JamesArthurHolland commented 5 years ago

I think dig.Name should be both an Invoke and Provide option, I had a look at the code but it's too much for me to change right now. Doing a tech test.

Decided to use https://github.com/sarulabs/di instead, they are doing named invocation nicely.