inject icon indicating copy to clipboard operation
inject copied to clipboard

Dependency Provider

Open orospakr opened this issue 12 years ago • 11 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. ;)

orospakr avatar Jan 06 '14 15:01 orospakr

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 avatar Jan 06 '14 17:01 codegangsta

@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)?

orospakr avatar Jan 07 '14 16:01 orospakr

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.

orospakr avatar Jan 08 '14 16:01 orospakr

:+1: 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())
    }
}

beefsack avatar Jan 12 '14 10:01 beefsack

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

stephanos avatar Feb 09 '14 19:02 stephanos

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

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!

stephanos avatar Feb 15 '14 16:02 stephanos

Cool! I will take a look at this soon

codegangsta avatar Feb 15 '14 18:02 codegangsta

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

stephanos avatar Feb 25 '14 16:02 stephanos

Thanks for the reminder!

Sent from my iPhone

On Feb 25, 2014, at 8:46 AM, Stephan Behnke [email protected] 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.

codegangsta avatar Feb 25 '14 16:02 codegangsta

@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 avatar Feb 26 '14 03:02 codegangsta

@codegangsta @orospakr @beefsack PR https://github.com/codegangsta/inject/pull/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

stephanos avatar Mar 02 '14 10:03 stephanos