Settings

Theme

Dependency Injection with Go

blog.parse.com

26 points by saj1th 11 years ago · 25 comments

Reader

argon81 11 years ago

Went down this path already.

It was OK for a while with a small codebase, but the problem is that you need to give up your simple direct Go code for magic reflection.

I discovered there are better and more idiomatic ways to do this type if thing without resorting to dependency injection.

  • sagichmal 11 years ago

    Strongly agree. A verbose `main` carries the benefit of being explicit and unambiguous. Components whose constructors take all of their dependencies are simple to reason about and straightforward to test.

    The code described in the article seems like it's burdened with patterns from other languages and ecosystems, and which are pretty nonidiomatic Go: "AppLoader"? The complexity of their final solution seems to be a result of not challenging those patterns and assumptions.

    • blt 11 years ago

      Yeah. I think the fear of a verbose `main` is wrong. It's OK for complicated code to look complicated. If your app needs to initialize a lot of complex network dependencies at startup, then maybe your startup routine is going to have a lot of code. No big deal.

      Software is complex. We all repeat, and try to follow, the mantra of writing simple code that does one thing. We succeed a lot of the time. But sometimes, we just need to do something f'n complicated. Better to express the complexity in type safe, debuggable code that everyone understands, than to hide it behind some framework.

      I think developers should be more accepting of a code base that's 95% clean and 5% messy. "The perfect is the enemy of the good."

  • elithrar 11 years ago

    > I discovered there are better and more idiomatic ways to do this type if thing without resorting to dependency injection.

    Agreed. Write a constructor[1] for your "app" or "context" type, and pass it around as needed. You both get to avoid globals and any magic dependency injection.

    As a bit of shameless self-promotion, I wrote an article about how to achieve this when writing HTTP handlers in Go: http://elithrar.github.io/article/custom-handlers-avoiding-g... - hint: create a struct type that accepts a pointer to your app struct and your handler type/http.Handler, or create your handlers as methods on your app type.

    [1]: http://www.jerf.org/iri/post/2929

    • saj1thOP 11 years ago

      Informative article elithrar - thanks!. Wondering why appContext was used instead of goji's Context/environment object ? Does not a context struct that holds all the handler dependencies makes the code harder to ponder and test(because the dependencies of a component would be unclear).

      Wondering whether testing/debugging would be a little less complex if we create separate handler-groups/components? That would make the main() very verbose with all the wiring - (one of the things the inject lib tries to solve).

      From what i understand there seems to be two line of thoughts.

      #1) Being verbose is good - Components whose constructors take all of their dependencies are simple to reason about and straightforward to test

      #2) When there are several binaries that share libraries - allocating memory, and wiring up the object graph becomes mundane and repetitive. Solving this by using a DI library like inject that doesn't add run-time overhead would be good. This doesn't have to happen at the cost of being difficult to test/reason-out.

      Guess each might have it's own place.

      • elithrar 11 years ago

        > Informative article elithrar - thanks!. Wondering why appContext was used instead of goji's Context/environment object ? Does not a context struct that holds all the handler dependencies makes the code harder to ponder and test (because the dependencies of a component would be unclear).

        Goji's context is a request context, and only exists for the lifetime of the request. Re-populating it at the beginning of every request would be a significant amount of overhead, and it's ultimately not designed for "life-of-application" variables. Request contexts like Goji's (or gorilla/context) are best used for passing short-lived data between middleware/handlers.

        You could ultimately create a series of smaller structs, but you would need a ton of types (and repetitive code) that satisfy http.Handler to achieve that.

        Memory allocation with this approach is minimal: you're only passing a struct pointer around once per request, which is about as good as it gets (better than a closure).

        Testing with this approach is also straightforward: you can populate the appContext instance with your test sessions|database|etc when running tests, and your handlers operate as if they've been passed the real thing.

        I've considered splitting out my handlers into a `handlers` package and the appContext struct/config structs into a `conf` package that handlers imports (and main initialises), and that's probably something I'll do in the near future since it's an easy change.

        It's certainly not the one way/single best way to do things, but I've found that aligning to interfaces (like http.Handler) and leaning on structs as much as possible helps keeps things easier to reason and mock out later.

chewxy 11 years ago

Does anyone think this is a little stoppy (there isn't a Go equivalent to "unpythonic")?

  • Mithaldu 11 years ago

    > there isn't a Go equivalent to "unpythonic"

    Of course there is. :) Python does good marketing and rephrased an existing word, but the pair you're looking for is:

    idiomatic / unidiomatic

  • saj1thOP 11 years ago

    Found it interesting that inject wires up the object graph and runs only once on application startup. Curious to know your thoughts around why this lib could be considered non-idiomatic

mutatio 11 years ago

"Typically the main() function would call the various init functions like InitMongoService"

Why muddy your main() when init() exists?

See: http://golang.org/ref/spec#Package_initialization

  • sagichmal 11 years ago

    And why use InitXxx at all, when you can create an initialized, locally-scoped MongoService with a constructor, and pass it to the things that need it?

    • mutatio 11 years ago

      I was referring to Go's magic func init()

      Putting that in your files, let's say:

        db.go func init() => handle global DB's...
      
        templates.go func init() => handle HTML templates...
      
        settings.go func init() => load some settings...
      
      All fired automatically prior to your applications main() entry point WITHOUT needing to pollute main() with initDB(); initTemplates(); initSettings() etc. etc.
      • nostrademons 11 years ago

        They're trying to pass the objects created at startup into other objects, so that dependencies are explicit and can be mocked out in unit tests. You can't do this with init(), which always takes no arguments and returns no values.

      • sagichmal 11 years ago

            > I was referring to Go's magic func init()
        
        I know you were; I'm arguing that components shouldn't need an explicit initialization step, but rather they should be initialized as part of their `NewFoo` constructor.
fishnchips 11 years ago

I honestly don't think there even was a problem to be solved. Constructors could just accept their dependencies (interfaces) as parameters.

  • shellac 11 years ago

    On larger code bases it can help a lot. You get to a point where you need that config object over in some random function and you start weaving it through constructors. At which point a simple DI system really appeals.

    DI code also tends to be more pleasant to test in my experience since it promotes loose coupling.

pjmlp 11 years ago

And thus the path to Go2EE slowly starts....

  • lmm 11 years ago

    Just as with Java, if you keep the language simple then the complexity still has to go somewhere.

iand 11 years ago

I'm interested in real examples of dependency injection being useful outside of hooking up testing objects. Anyone got any links to interesting examples?

  • eknkc 11 years ago

    Martini (https://github.com/go-martini/martini) uses DI for everything. I's supposed to be freaking slow though. But it might not be a real world problem.

    • iand 11 years ago

      I guess I should have been clearer - I mean examples where you actually want different dependencies for the same application.

      All uses of DI that I have seen have a single configuration that basically never changes. The DI is used for hooking up test objects, but not for reconfiguring the production app.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection