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

Dependency Provider #10

Open
orospakr opened this issue Jan 6, 2014 · 11 comments
Open

Dependency Provider #10

orospakr opened this issue Jan 6, 2014 · 11 comments

Comments

@orospakr
Copy link

orospakr commented Jan 6, 2014

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. ;)

@codegangsta
Copy link
Owner

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.

@orospakr
Copy link
Author

orospakr commented Jan 7, 2014

@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
Copy link
Author

orospakr commented Jan 8, 2014

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.

@beefsack
Copy link

👍 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())
    }
}

@stephanos
Copy link

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

@stephanos
Copy link

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!

@codegangsta
Copy link
Owner

Cool! I will take a look at this soon

@stephanos
Copy link

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

@codegangsta
Copy link
Owner

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
Copy link
Owner

@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

@stephanos
Copy link

@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

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

No branches or pull requests

4 participants