Rolling your own dependency injection

Anders Sveen
5 min readApr 18, 2018

--

https://pixabay.com/en/technology-illuminated-abstract-3243375/

TL;DR — Dependency Injection (DI) is not magic. It’s code. And writing your own DI isn’t that hard. By doing it yourself you get fast start up times and really efficient feedback when doing Test-driven development (TDD). Even for integration tests with HTTP and databases.

Just get started, or read this post for some experiences we have made doing it.

A complete “running” application (Kotlin and Gradle) can be found here: https://github.com/PorterAS/dependency-injection

Java has been moving towards “light weight” for a long time. It used to be a way to describe that you were not using the big fat app server. But after I stopped using the app servers, I have also found that I constantly look for ways to reduce the run time-overhead and complexity of the frameworks. Sometimes that means changing how I use the framework, other times it means choosing a different one or no framework at all.

Living with less frameworks I find that:

  • I spend less time on Google figuring out which annotation or XML/JSON/YAML element to specify.
  • I TDD a lot more. Because I control how much is being loaded and when, I only load what’s necessary and it’s fast. I can decide to make the feedback loop efficient even if I am writing automated tests that does actual HTTP and database calls.
  • I write efficient code that is maintainable. I find better code constructs that are efficient at doing what I need in the given context without always following the same patterns the framework recommends.
  • I re-factor more. Some times when I re-factor, the specific patterns of the framework gets in the way. Without framework annoyances it is easier and more fun to do re-factorings.

I still use open source libraries (it’s a subtle distinction to frameworks) a lot, and I really think they’ve brought the programming community a long way. I have become more and more of a skeptic, especially of the frameworks that encompass business logic. There are probably several reasons for that:

  • A bit of hubris. I now think I can make anything myself. 😉 I know more stuff now, but I can also see through what used to seem like magic and understand what’s happening. Some stuff just aren’t hard problems. I try to balance it.
  • I have seen how much of a constraint little things can be when evolving software. While the standard patterns and frameworks help you, it’s a bit like keeping the training wheels on. You can only go so fast…
  • I have seen a lot of the patterns in the frameworks I have used over the years. And (I think) I know how to use them. I know when they add value and when they are just getting in the way.
  • I am a TDD fanatic. Well not really a fanatic, but my way of working really depends on a tight feedback loop. I am not going to force you to write tests first, or even keep all of my tests when a feature is done. But I need them while developing. They come with a cost, especially for maintenance.
  • Some frameworks actually add a lot of overhead to the run time, and especially start up times. This also plays into me TDDing and writing (some) tests that actually start HTTP servers and do actual HTTP calls.

When I start a new code base now, I try to start small and expand as I need it. Being a Java dude: public static void main, load some properties from file, start the server and write some tests. Simple as that. 😉

I have done this enough times for some patterns to emerge. Most of them are common in many of the DI frameworks as well.

Load a complete set of environment settings as early as possible.

This gives you one place to handle defaults, overrides and multiple sources. This holds for simple config values, defaults for injected objects comes at a later stage.

Once you pass this stage you know everything is set for the next stage. You will usually want to load from a property file if running tests (get the same result in your IDE and shell), some defaults, and let environment variables override it all if found. Do required and validity checks.

Be wary of things that are slow to load, like loading config from an URL. Maybe even have some defaults for when it fails?

Load variables into config object

Put the resolved settings into an container carrying all the configuration and pass it to the dependency injection.

Main method with properties loaded then wired together and started.

Do dependency injection when you know that everything is in place.

Wiring of your dependencies will be complex enough, not having to have lots of if/else for the variables as well helps you keep the code clean and reason about the injection. If you’re using a typed language you’ll also get some help from your “container” above with the types that are injected. Hint: Don’t inject Strings where you can avoid it.

You have the information, time to decide whether to use defaults for services or something else.

When I am writing tests I will inject stubs (the defaults in the DependencyInjectionApplicationContext constructor) where it is necessary:

Set up a context with stubs for testing

Run tests

Did I mention it’s fast? 😃 Running a build with tests in Gradle (I’ve truncated some logging with “…” ):

➜  dependency-injection git:(master) ./gradlew clean build> Task :junitPlatformTest
09:19:38.122 [Thread-1] INFO org.eclipse.jetty.util.log - Logging initialized @542ms to org.eclipse.jetty.util.log.Slf4jLog
09:19:38.159 [Thread-1] INFO s.e.jetty.EmbeddedJettyServer - == Spark has ignited ...
...
09:19:38.192 [Thread-1] INFO org.eclipse.jetty.server.Server - Started @615ms
09:19:38.520 [Thread-12] INFO s.e.jetty.EmbeddedJettyServer - >>> Spark shutting down ...
...
09:19:38.525 [Thread-12] INFO s.e.jetty.EmbeddedJettyServer - done
09:19:38.526 [Thread-13] INFO s.e.jetty.EmbeddedJettyServer - == Spark has ignited ...
...
09:19:38.528 [Thread-13] INFO org.eclipse.jetty.server.Server - Started @950ms
09:19:38.533 [Thread-22] INFO s.e.jetty.EmbeddedJettyServer - >>> Spark shutting down ...
...
09:19:38.535 [Thread-22] INFO s.e.jetty.EmbeddedJettyServer - done
BUILD SUCCESSFUL in 3s
6 actionable tasks: 6 executed

We are actually starting the Spark server several times. When you have a lot of tests you would structure it in a way that you start the server (share it between tests) as few times as possible. In one of our current code bases we launch it 3 times. It depends on the complexity of your tests and what you need to stub.

Is this pure bliss? Of course not. But I spend less time Googling, and more time writing code. I can see exactly what goes into the wiring in my code, and I do the things I need to run tests efficiently. And it starts fast. No lengthy classpath scanning. 😉

Complete example with running code: https://github.com/PorterAS/dependency-injection

--

--

Anders Sveen
Anders Sveen

Written by Anders Sveen

Passionate agile developer and mentor for hire @ Mikill Digital

Responses (1)