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.
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.
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."
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.
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.
Of course there is. :) Python does good marketing and rephrased an existing word, but the pair you're looking for is:
idiomatic / unidiomatic
Why muddy your main() when init() exists?
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.DI code also tends to be more pleasant to test in my experience since it promotes loose coupling.
http://commandcenter.blogspot.com/2014/01/self-referential-f...
And you should probably avoid situations where you suddenly need config objects in 'random places'. This could be a symptom of chaotic API design. But then again, whatever works for you.
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.