Mon 19 Mar 2018 — Tue 08 May 2018

A Decoupling Too Far

This document is a criticism of the Spring Framework for Java, and why I think it is a bad choice to incorporate into a computer program written for any purpose.

Spring has a culture of maximum generality at all costs, which has left it with a bad design.

I'd argue that:

  1. Inversion of control (also called depedency inversion) is a useful principle to have in your mind when you write software.
  2. Using inversion of control everywhere is bad design, which abdicates responsibility for the decisions about which parts of your program should actually be configurable.
  3. If the answer to "Which parts of my program should be configurable?" really is "All of them!", then consider that doing the inversion of control by hand might still be less pain than using a framework.
  4. If you decide that you really must use a Java dependency injection framework, Guice is much better than Spring. Alternatively, consider using OSGI to dynamically load in an updated module without downtime.

Try embedding an interpreted language on top of your C or Java core — glue and configuration is what interpreted languages are designed for, and they're good at it.

Inversion of Control

The core principle of Spring is the concept of Inversion of Control. The principle here is that, when we have some interaction between parts of code, the calling code has responsibility for providing any dependencies. The called code should never pull another module out of thin air.

To add to this, we don't depend on a specific implementation, but rather on an Java interface which provides the subset of functionality which we require.

IOC's Problem: Large Function Signatures

I agree that inversion of control is a useful principle to be aware of. It does improve testability and reusability, and it can help to avoid spooky action at a distance. In my opinon, we should aim to pass stateful components in particular down the call hierarchy, rather than extracting them from global variables, static references, or singletons.

I disagree, however, that you should always use inversion of control. I certainly don't think you should design a computer program using inversion of control as the central theme.

Make code reusable, general or flexible has a usability cost. Each parameter you add to a function signature increases the amount of thinking you have to do when you use it. Keeping the same number of parameters, but making them into larger, more complex objects has a similar effect.

Choosing what to pass into a method signature is a question of design and correct data flow. It's a decision which permeates our software projects — we make it hundreds or thousands of times per program.

Dependency Injection Frameworks

Dependency injection frameworks like Spring attempt to bypass the problem of large method signatures. They do this by putting all the possible dependencies in a big lookup table going from interface to possible implementation. They then use that table to automatically call the required functions (usually Java constructors), inspecting the function signature and passing in the correct implementation.

There is usually some extra configuration to break ties in cases when we had multiple possible implementations of a particular interface.

This is a failure. We haven't really made our function signatures smaller, we've just hidden all of the complexity in our lookup table.

By using a dependency injection framework, we've covered the symptom of bad design that made programming immediately more difficult and time consuming. We didn't fix the root design problem though. If we keep programming this way, then our program is going to accumulate cross-connections between modules over time, and become irrevocably tangled.

If we stick strictly to our dependency inversion principles, this tangle will be made of interfaces rather than implementations. It's still a mess though.

Configuration — Plumbing — Enterprise

Java is probably the wrong tool for the enterprise development job, and the fact they are so often associated is probably unfortunate for both.

Java has presumably been selected for its good characteristics. Among these are that it's reasonably fast, reliable, secure (as long as you don't use the web plugin), and maintainable.

(Java is also easy to hire fire, and easy to train for. These definitely are desirable attributes for any business, and not to be sniffed at.)

Unhappily, these traits which the enterprise thinks it wants aren't actually the ones it needs.

The actual enterprise environment involves:

  • Lots of departments with difference circumstances.
  • Lots of different software to meet those various circumstances.

And therefore:

  • Lots of configuration and customisation.
  • Lots of glue code to make all these different programs talk to each other.

Yes, what the enterprise really needs is glue code. That could mean a dynamic programming language, shell scripts, or (less realistically) a quick fix–compile–test–deploy cycle.

Configuration vs Programming

There isn't a hard line between configuration and programming. Both of them are ultimately writing code for the computer to execute.

If your configuration is simple, you can read it in easily from a flat file or some command line arguments.

If you need unknown amounts of flexibility after your application is deployed, then your configuration can get very complex over time. At some point, modifying it is going to become of equivalent difficulty to writing a computer program. At that point, if you're using Spring, you've made things harder for yourself, because Spring's configuration language isn't a good general-purpose programming language.

Stated differently: the apparent productivity benefits of wiring together configurable components disappear, because your configuration becomes a bigger project than the components themselves.

Aside: Performance

It's not part of the main argument, but Spring's has notoriously slow startup performance, and that matters.

Setting up quick feedback cycles makes for much more productive development, and Spring is definitely slow enough to interfere with that.

Aside: Using Dependency Injection together with Interpreted Languages

If you're doing this, you are terrible. Stop it.