codegangsta / inject

Dependency injection for go

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Dependency Provider

orospakr opened this issue · comments

It would be very nice if inject could control the dependency construction process, rather than the developer needing to manually instantiate them in the correct order and Map()'ing them in. This could be thought of as evaluating a dependency graph.

Something like this:

type Provider interface {
    Provide(ifacePtr interface{}, constructor func(*TypeMapper) interface{})
}

Then the TypeMapper, on request of a type that it does not have in is values map, would invoke the Provider to create it. Could also do some sort of infinite regression protection, too, in the event of an inadvertent dependency loop.

Alas, I can't think of a more declarative way to do it, and interface{} makes me sad.

What do you think, @codegangsta? I apologise for the presumptuous design ticket. ;)

This can be a great pattern to have. The only issue with this is that we actually won't know whether struct values are nonexistent or zero valued. Which is not too bad of an issue since who would want to construct something that is passed by value. I can definitely see this being used.

We can also pass in the instance/function that is being called/applied so we can have some sort of factory pattern happening.

@codegangsta doesn't Apply() have the same issue already (not knowing the difference between unset/default values)? It seems that just stomping over them (provided they are tagged, of course!) as Apply() does now is fine. Or do I misunderstand?

Hm, as for your second notion, are you saying passing in an separate receiver pointer to go with the method (on account of Golang lacking a bound function pointer concept)?

Another feature worth considering is having it offer a singleton setting, in which when off the Provider will reinvoke the constructor function to produce another item for each request, rather than constructing only one item and storing it for repeated use in values.

👍 for this feature request, one major feature of lazy construction of dependencies is it makes the order of dependency mapping much less important, which is something that would be very handy for one of my current projects.

My initial thought was to have some arbitrary struct to wrap the provider function in, and that could just be passed to Map as usual. A very crude example being:

import (
    "fmt"
    "reflect"
)

type DependencyProvider struct {
    Provider interface{}
}

func CheckDependencyProvider() {
    // Mock up some stuff
    var dpi interface{}
    dpi = DependencyProvider{func(someStr string, someInt int) float32 {
        return 5.5
    }}
    // Check if we have a dependency provider
    // Outputs: Provides "float32"
    if dp, ok := dpi.(DependencyProvider); ok {
        fmt.Printf("Provides %#v\n",
            reflect.ValueOf(dp.Provider).Type().Out(0).String())
    }
}

This would be great!
Is there any progress on this?

I just took a stab at implementing something like this today: 238f77d

It is an early, simple draft. Maybe not particularly pretty, efficient or flexible - but a working prototype that allows to use factories (in this version simple functions) to specify how injected values should be constructed.

It's a straight-forward recursive algorithm. I just needed to extend the current Get in order to know whether the iteration is at the 'bottom' (injector that is no child) or not.

There are tests for various scenarios, even detecting dependency loops. And the error gives detailed information where the loop occurred.

Tell me what you think!

Cool! I will take a look at this soon

@codegangsta Did you have a chance to check it out yet?
I would love to use this very soon!

Thanks for the reminder!

Sent from my iPhone

On Feb 25, 2014, at 8:46 AM, Stephan Behnke notifications@github.com wrote:

Did you have a chance to check it out yet?
I would love to use this very soon!


Reply to this email directly or view it on GitHub.

@stephanos The commit looks good enough to put up a PR. Please put one together and I will do a proper review :)

Thanks for putting this together

@codegangsta @orospakr @beefsack
PR #16 is available now.

Tell me what you think. I think it's a good first, simple approach with lots of benefits. But there are a few things that I see following up.

Provider

By using a struct like suggested above there could be additional options to configure the provider's behaviour:

type DependencyProvider struct {
    Provider interface{}
    DoNotCache bool
}

Error Handling

The provider should be allowed to optionally return an error. I haven't implemented this yet because I'm not sure how this would work together with Martini. But the gist is:

stringFactory := func(i int) (s string, err error) {
    if i >= 0 {
        s = fmt.Sprintf("%v", s)
    } else {
        err = fmt.Errorf("invalid integer")
    }
    return
}

injector1 := New().Map(-1).Map(stringFactory)
_, err := injector1.Invoke(func(s string) {}) // ERROR !

injector2 := New().Map(1).Map(stringFactory)
_, err := injector2.Invoke(func(s string) {}) // no error