
Compile-Time DI vs. Run-Time DI - dimes
https://dimes.github.io/blog/compile-time-di-vs-run-time-di
======
benbjohnson
I don't understand why you need a framework for dependency injection in Go.
I've written Go for years and I just use the `main` package to instantiate and
wire up everything. It's really straightforward, easy to debug, and there's
nothing to generate. It's all type checked at compile time.

I'm not trying to rain on the author's parade. I genuinely don't understand
the benefit—especially in relation to the complexity of adding an extra
dependency and layer to my application.

~~~
strulovich
How many active contributors and lines of code does your projects have?

I’ve found that the advantages for DI frameworks (besides encouraging more
unit tests and enabling them) to be more valuable for bigger and messier
projects.

~~~
sagichmal
I find precisely the opposite, that as a project gains (and loses, and gains
again) maintainers, the indirection and mental overhead that DI frameworks
necessarily bring to the table vastly outweigh whatever perceived benefit
there was in introducing them.

Conversely, a declarative, step-wise func main, with components manually
constructed and passed to each other as dependencies where necessary, is
sometimes tedious, but never confusing, and always refreshingly easy to
maintain, no matter how much time you've spent with the project.

~~~
tempguy9999
I've literally no idea what the second paragraph means but I suspect it's
quite important. Could you point to an example of this being done? Thanks.

~~~
twic
This sounds like the main methods i write (in Java, also not using a DI
framework). Like this:

    
    
      var database = new DatabaseFacade(config.get("db.host"), config.get("db.port", Integer::parseInt));
      var users = new UserRepository(database);
      var catalogue = new ProductRepository(database);
      var cart = new ShoppingCartController(users, catalogue);
    

It's imperative code, of course, but it's sort of declarative in that all the
code is doing is wiring things up. It's step-wise in that it does one simple
thing after another; it might be quite long, as there are a lot of components
to create, but it can be understood locally. Components are manually
constructed, by calling constructors, rather than via reflection done by the
framework. Components are passed to each other as parameters to establish
dependencies, rather than there being some sort of rule-driven lookup, as a
framework would do. It is indeed pretty tedious to read, as it's just lots of
constructor calls, with no thrilling action. But it's so simple it's never
confusing (and you get to use the full power of the IDE to navigate it,
jumping to definitions and uses etc). And, as such, easy to maintain.
Definitely refreshingly so if you've come from Spring or Guice.

~~~
qes
Would you consider this viable in a monolith, though?

The primary application I work on has hundreds of classes that get
dependencies injected through DI. Many can be and are singletons, but many -
including some very commonly used ones - cannot.

If I manually wired dependencies it would add thousands of lines of code, and
there are some where if I changed the constructor parameters I would have to
modify 1000+ call sites.

And this isn't even a particularly massive or complex application.

~~~
twic
The largest application where we use this pattern has 131737 lines of code
(according to cloc), and four 'main' classes which do dependency injection,
which create roughly 155, 145, 111, and 95 components. Those files total 8949
lines of code. They're doing more than just injection, though, there's quite a
bit of code for other kinds of setup and configuration.

Remember that this setup code is just code, so if you find that adding a
constructor parameter means modifying a thousand call sites, you should
probably refactor it a bit first, so that it doesn't.

That being said, this approach to DI does take some tedious manual work to
maintain. But by paying that cost, you get to take a magical DI framework out
of the picture entirely.

------
stabbles
What this blog post does not address is the joy of configuring DI with XML
files /s

Isn't it bizarre how software developers have taken runtime DI to such
extremes that it was considered a good idea to configure services in XML or
YAML -- basically another language, that has to be parsed, can be malformed,
etc?

~~~
duality
The creators of Guice mention how configuring the dependency graph in Java was
a big advantage it offers over older XML-based frameworks like Spring.

~~~
douglasisshiny
Spring has allowed for java configuration for some time.

------
mcnichol
Your point of breaking things as early in the process really is the winner.

Catching things early and often. Tooling that shifts things further left in
the pipeline is my go-to default.

~~~
chris_wot
Do you think it is rust that popularized this?

~~~
maxxxxx
Why Rust? Strongly typed languages had this capability for a long time.

~~~
oldgun
I believe what he means is that Rust takes this a step further by ensuring
memory safety at compile time.

~~~
jerf
Rust is merely the latest in a long line of efforts in that direction. It may
have located a particularly nice point in the cost/benefits space, and the
borrow semantics I believe are novel, but it's well inline with many efforts
to make compile time errors out of as many things as possible, from a lot of
C++ stuff, Ada, Eiffel, Haskell, a variety of proof methods, the list goes on,
and even the things I list are themselves exemplars of entire streams of
thought on the idea rather than a complete list, of course.

(This is not a criticism of Rust. It is not a criticism of a work to place it
correctly in its historical context. Very few things consist entirely of novel
ideas, and very few of those are any good, if any.)

~~~
maxxxxx
Agreed. Rust is another step in the right direction but I don't think it
invented a new paradigm.

------
kangnkodos
DI is Dependency Injection.

------
kazagistar
Java has a fairly nice compile time DI framework in Dagger. However, it feels
like it is missing a compile time version of the rest of the web stack:
compile time routing and compile time template compilation, for example. Does
anyone have any suggestions to fill out this stack?

~~~
snuxoll
It’s not perfect by any means, but there is Ktor as an example. Use
kotlinx.html for your templates and everything is defined at compile time.

