Flying while programming in modern Java and C++

Hi, my name is Ján! In this corner of the web I vent about everyday struggles of being a private pilot and a software engineer.

Back

Vanilla Java: you do not need a DI container

Vanilla Java is a series of posts advocating for pure Java first, then purpose built utilities with JDK, and only adding third-party dependencies as a last resort.

What is a Dependency Injection (DI) container?

DI in its simplest form is a design pattern describing application's object graph lifetime. A DI container is an opinionated implementation of this design pattern, i.e. a framework. Throughout my Java software engineering career I gained hands-on experience with Guice, Spring, various J2EE implementations since v2.x up, Grails and many others. After initial euforia which laster for nearly a decade I realized they basically all become a maintenance nightmare as the project grows.

After Java 8 became wide-spread, which is where I draw a line for "modern" Java, there came a realization that none of the DI containers are necessary at all. The language has evolved to the point vanilla Java is good enough.

SRDI pronounced "SERDI", hard "R": Sender - Receiver Dependency Injection

For networking enthusiasts the terms SEnder and Receiver immediately ring the bell. These terms are borrowed from the RFC 9293 and there is definitely prior art.

Am I re-inventing the wheel? Not at all. Any of the aforementioned frameworks are an abstract, where your own application is an instance. I aim to remove the abstract away and go straight to the instance. The DI design pattern servers as a reference.

Minimal application

A short refresher on com.sun.net.httpserver.HttpServer in my older post.

Using the JDK's built in HTTP server a trivial main(String[] args):

package eu.freshmen.srdi;

import com.sun.net.httpserver.HttpServer;

import eu.freshmen.srdi.sender.Logger;
import eu.freshmen.srdi.sender.Server;

public class Application {

	interface Dependencies extends Server {
		// a Receiver: declares all dependencies via extension
	}

	private final Logger log;
	private final HttpServer server;

	Application(Dependencies deps) {
		log = deps; // downcast
		server = deps.server(); // instantiation
	}

	void start() {
		final var addr = server.getAddress();
		log.debug("Starting on " + addr.getHostName() + ":" + addr.getPort() + ", SIGKILL to stop.");
		server.start();
	}

	public static void main(String[] args) {
		final var app = new Application(new Dependencies() {
			// trivial Sender: all dependencies provide default implementations
		});
		app.start();
	}

}
Why vanilla JDK?
With pure interfaces and language's backwards compatibility, there is near zero chance SRDI stops working during upgrades and zero chance of forced framework upgrades.
Where does SRDI shine?
SRDI is a compile time DI. Meaning nothing builds unless all the dependencies are resolved. There is no runtime classpath scan, annotations, name resolution, unexpected surprises, etc.
Startup time only depends on how quickly instantiation of dependencies works, near zero overhead.
Dependency overrides or profiles are clear from the interface hierarchy and any IDE can visualize it.
Where does SRDI fall short?
SRDI does not have a concept of scopes, meaning everything lives as long as the process does.
SRDI needs an explicit memoize component in case a singleton is desired.
request scope needs to be hand rolled and is generally application specific.

Sources

  1. Spring Reference: IoC and DI
  2. Guice Dependency Injection
  3. Learn .NET Dependency Injection
  4. Manning: Writing Maintainable, Loosely-Coupled Code
  5. Java Illegal Reflective Access (module opens)

Contact

Tell us!
Menu

Items marked with * are required