Twice recently we have had “fun” trying to get things using HK2 (Jersey), to place nicely with code built using Guice and Spring. This has renewed my appreciation for code written without DI frameworks.
The problem with (many) DI frameworks.
People like to complain about Spring. It’s an easy target, but often the argument is a lazy “I don’t like XML, it’s verbose, and not fashionable, unlike JSON, or YAML, or the flavour of the month”. This conveniently ignores that it’s possible to do entirely XML-less Spring. With JavaConfig it’s not much different to other frameworks like Guice. (Admittedly, this becomes harder if you try to use other parts of Spring like MVC or AoP)
My issue with many DI frameworks is the complexity they can introduce. It’s often not immediately obvious what instance of an interface is being used at runtime without the aid of a debugger. You need to understand a reasonable amount about how the framework you are using works, rather than just the programming language. Additionally, wiring errors are often only visible at runtime rather than compile time, which means you may not notice the errors until a few minutes after you make them.
Some frameworks also encourage you to become very dependent on them. If you use field injection to have the framework magically make dependencies available for you with reflection, then it becomes difficult to construct things without the aid of the framework – for example in tests, or if you want to stop using that framework.
Even if you use setter or constructor injection, the ease with which the framework can inject a large number of dependencies for you allows you to ignore the complexity introduced by having excessive dependencies. It’s still a pain to construct an object with 20 dependencies without the framework in a test, even with constructor or setter injection. DI frameworks can shield us from the pain that is useful feedback that the design of our code is too complex.
What do I want when doing dependency injection? I have lots of desires but these are some of the most important to me
- Safety – I would like it to be a compile time error to fail to satisfy a dependency
- Testability – I want to be able to replace dependencies with test doubles where useful for testing purposes
- Flexibility – I would like to be able to alter the behaviour of my program by re-wiring my object graph without having to change lots of code
It’s also nice to be able to build small lightweight services without needing to add lots of third party dependencies to get anything done. If we want to avoid pulling in a framework, how else could we achieve our desires? There are a few simple techniques we can use which only require pure Java, some of which are much easier in Java 8.
I’ve tried to come up with a simple example that might exist if we were building a monitoring system like Nagios. Imagine we have a class that is responsible for notifying the on call person for your team when something goes wrong in production.
Manual Constructor Injection
class IncidentNotifier {
final Rota rota;
final Pager pager;
IncidentNotifier(Pager pager, Rota rota) {
this.pager = pager;
this.rota = rota;
}
void notifyOf(Incident incident) {
Person onCall = rota.onCallFor(incident.team());
pager.page(onCall, "Oh noes, " + incident + " happened");
}
}
I would expect that the Pager and Rota will have dependencies of their own. What if we want to construct this ourselves, it’s still fairly straightforward. Not much more verbose to to do explicitly in Java.
public static void main(String... args) {
IncidentNotifier notifier = new IncidentNotifier(
new EmailPager("smtp.example.com"),
new ConfigFileRota(new File("/etc/my.rota"))
);
}
The advantage of this over automatically injecting them with a framework and @Inject annotations or an XML configuration is that doing it manually like this allows the compiler to warn us about invalid configuration. To omit one of the constructor arguments is a compile failure. To pass a dependency that does not satisfy the interface is also a compile failure. We find out about the problem without having to run the application.
Let’s increase the complexity slightly. Suppose we want to re-use the ConfigFileRota instance, within several object instances that require access to the Rota. We can simply extract it as a variable and refer to it as many times as we wish.
public static void main(String... args) {
Rota rota = new ConfigFileRota(new File("/etc/my.rota"));
Pager pager = new EmailPager("smtp.example.com");
IncidentNotifier incidentNotifier = new IncidentNotifier(
pager,
rota
);
OnCallChangeNotifier changeNotifier = new OnCallChangeNotifier(
pager,
rota
);
}
Now this will of course get very long when we start having a significant amount of code to wire up, but I don’t see this an argument not to do the wiring manually in code. The wiring of objects is just as much code as the implementation.
There is behaviour emergent from the way in which we wire up object graphs. Behaviour we might wish to test, and have as much as possible checked by the compiler.
Wanting this configuration to be separated from code into configuration files to make it easier to change may be a sign that you are not able to release code often enough. There is little need for configuration outside your deployable artifact if you are practising Continuous Deployment.
Remember, we have the full capabilities of the language to organise the resulting wiring code. We can create classes that wire up conceptually linked objects, or modules.
Method-Reference Providers
Now suppose we want to send the notification time in the page. We might do something like
pager.page(onCall, "Oh noes, " + incident + " happened at " + new DateTime());
Only now it is hard to test, because the time in the message will change each time the tests run. Previously we might have approached this problem by creating a Factory type for time, maybe a Clock type. However, in Java 8, thanks we can just use constructor method references which significantly reduces the boilerplate.
IncidentNotifier notifier = new IncidentNotifier(
new EmailPager("smtp.example.com"),
new ConfigFileRota(new File("/etc/my.rota")),
DateTime::new
);
class IncidentNotifier {
final Rota rota;
final Pager pager;
final Supplier clock;
IncidentNotifier(Pager pager, Rota rota, Supplier clock) {
this.pager = pager;
this.rota = rota;
this.clock = clock;
}
void notifyOf(Incident incident) {
Person onCall = rota.onCallFor(incident.team());
pager.page(onCall, "Oh noes, " + incident + " happened at " + clock.get());
}
}
Test Doubles
It’s worth pointing out at this point how easy Java 8 makes it to replace this kind of dependency with a test double. If your collaborators are single-method interfaces then we can cleanly stub out their behaviour in tests without using a mocking framework.
Here’s a test for the above code that asserts that it invokes the page method, and also checks the argument, both in the same test to simplify the example. The only magic is the 2 line static method on the Exception. I have used no dependencies other than JUnit.
@Test(expected=ExpectedInvocation.class)
public void should_notify_me_when_I_am_on_call() {
DateTime now = new DateTime();
Person benji = person("benji");
Rota rota = regardlessOfTeamItIs -> benji;
Incident incident = incident(team("a team"), "some incident");
Pager pager = (person, message) -> ExpectedInvocation.with(() ->
assertEquals("Oh noes, some incident happened at " + now, message)
);
new IncidentNotifier(pager, rota, () -> now).notifyOf(incident);
}
static class ExpectedInvocation extends RuntimeException{
static void with(Runnable action) {
action.run();
throw new ExpectedInvocation();
}
}
As you can see the stubbings are quite consise thanks to most collaborators being single method interfaces. This probably isn’t going to remove your need to use Mockito or JMock, but stubbing with lambdas is handy where it works.
Partial Application
You might have noticed that the dependencies that we inject in the constructor could equally be passed to our notify method, we could pass them around like this. It can even be a static method in this case.
Incident incident = incident(team("team name"), "incident name");
FunctionalIncidentNotifier.notifyOf(
new ConfigFileRota(new File("/etc/my.rota")),
new EmailPager("smtp.example.com"),
DateTime::new,
incident
);
class FunctionalIncidentNotifier {
public static void notifyOf(Rota rota, Pager pager, Supplier clock, Incident incident) {
Person onCall = rota.onCallFor(incident.team());
pager.page(onCall, "Oh noes, " + incident + " happened at " + clock.get());
}
}
If we have to pass around all dependencies to every method call like this it’s going to make our code difficult to follow, but if we structure the code like this we can partially apply the function to give us a function with all the dependencies satisfied.
Incident incident = incident(team("team name"), "incident name");
Notifier notifier = notifier(Partially.apply(
FunctionalIncidentNotifier::notifyOf,
new ConfigFileRota(new File("/etc/my.rota")),
new EmailPager("smtp.example.com"),
DateTime::new,
_
));
notifier.notifyOf(incident);
There’s just a couple of helpers necessary to make this work. First, a Notifier interface that can be created from a generic Consumer<T>. Static methods on interfaces come to the rescue here.
interface Notifier {
void notifyOf(Incident incident);
static Notifier notifier(Consumer notifier) {
return incident -> notifier.accept(incident);
}
}
Then we need a way of doing the partial application. There’s no support for this built into Java as far as I am aware, but it’s trivial to implement. We just declare a method that accepts a reference to a consumer with n arguments, and also takes the arguments you wish to apply. I am using an underscore to represent missing values that are still unknown. We could add overloads to allow other parameters to be unknown.
class Partially {
static Consumer apply(
QuadConsumer f,
T t,
U u,
V v,
MatchesAny _) {
return w -> f.apply(t,u,v,w);
}
}
interface QuadConsumer {
void apply(T t, U u, V v, W w);
}
class MatchesAny {
public static MatchesAny _;
}
Mixins
Going back to the object-oriented approach. Suppose we want our Pager to send emails in production but just print messages to the console if we are running it on our workstation. We can create two implementations of Pager, an EmailPager and a ConsolePager, but we want to use an implementation based on what environment we are running in. We can do this by creating an EnvironmentAwarePager which decides which implementation to use at Runtime.
class EnvironmentAwarePager implements Pager {
final Pager prodPager;
final Pager devPager;
EnvironmentAwarePager(Pager prodPager, Pager devPager) {
this.prodPager = prodPager;
this.devPager = devPager;
}
public void page(Person onCall, String message) {
if (isProduction()) prodPager.page(onCall, message);
else devPager.page(onCall, message);
}
boolean isProduction() { ... }
}
But what if we want to test the behaviour of this environment aware pager, to be sure that it calls the production pager when in production. To do this we need to extract the responsibility of checking whether we are running in the production environment. We could make a collaborator, but there is another option (which I’ll use for want of a better example) – We can mix in functionality using an interface.
interface EnvironmentAware {
default boolean isProduction() {
// We could check for a machine manifest here
return false;
}
}
Now our Pager becomes
class EnvironmentAwarePager implements Pager, EnvironmentAware {
final Pager prodPager;
final Pager devPager;
EnvironmentAwarePager(Pager prodPager, Pager devPager) {
this.prodPager = prodPager;
this.devPager = devPager;
}
public void page(Person onCall, String message) {
if (isProduction()) prodPager.page(onCall, message);
else devPager.page(onCall, message);
}
}
We can use isProduction without implementing it.
Now let’s write a test that checks that the production pager is called in the production environment. Here we extend EnvironmentAwarePager to override its production-awareness by mixing in the AlwaysInProduction interface. We stub the dev pager to fail the test because we don’t want that to be called, and stub the prod pager to fail the test if not invoked.
@Test(expected = ExpectedInvocation.class)
public void should_use_production_pager_when_in_production() {
class AlwaysOnProductionPager
extends EnvironmentAwarePager
implements AlwaysInProduction {
AlwaysOnProductionPager(Pager prodPager, Pager devPager) {
super(prodPager, devPager);
}
}
Person benji = person("benji");
Pager prod = (person, message) -> ExpectedInvocation.with(() -> {
assertEquals(benji, person);
assertEquals("hello", message);
});
Pager dev = (person, message) -> fail("Should have used the prod pager");
new AlwaysOnProductionPager(prod, dev).page(benji , "hello");
}
interface AlwaysInProduction extends EnvironmentAware {
default boolean isProduction() { return true; }
}
Using mixins here is a bit contrived, but I struggled to come up with an example that was both not contrived and sufficiently brief to illustrate the point.
Cake Pattern
I mention mixins partly because it leads onto the Cake Pattern
The cake pattern can make it a bit easier to wire up more complex graphs, when compared to manual constructor injection. While it does also add quite a lot of complexity in itself, we do at least retain a lot of compile time checking. Failing to satsify a dependency will be a compilation failure.
Here’s what an example application using cake would look like that sends an page about an incident specified with command line args.
public class Example {
public static void main(String... args) {
ProductionApp app = () -> asList(args);
app.main();
}
}
interface ProductionApp extends
MonitoringApp,
DefaultIncidentNotifierProvider,
EmailPagerProvider,
ConfigFileRotaProvider,
DateTimeProvider {}
interface MonitoringApp extends
Application,
IncidentNotifierProvider {
default void main() {
String teamName = args().get(0);
String incidentName = args().get(1);
notifier().notifyOf(incident(team(teamName), incidentName));
}
}
interface Application {
List args();
}
Here we’re using interfaces to specify the components we wish to use in our application. We have a MonitoringApp interface that specifies the entry point behaviour. It sends a notification using the command line arguments. We also have a ProductionApp interface that specifies which components we want to use in this application.
If we want to replace a component – for example to print messages to the console instead of sending an email when running it on our workstation it’s just a matter of replacing that component –
interface WorkstationApp extends
MonitoringApp,
DefaultIncidentNotifierProvider,
ConsolePagerProvider, // This component is different
ConfigFileRotaProvider,
DateTimeProvider {}
This is checked compile time. If we were to not specify a PagerProvider at all we’d get a compile failure in our main method when we try to instantiate the WorkstationApp. Admittedly, not a very informative message if you don’t know what’s going on (WorkstationApp is not a functional interface, multiple non-overriding abstract methods found in com.benjiweber.WorkstationApp.)
For each thing that we want to inject, we declare a provider interface, which can itself rely on other providers like
interface DefaultIncidentNotifierProvider extends
IncidentNotifierProvider,
PagerProvider,
RotaProvider,
ClockProvider {
default IncidentNotifier notifier() {
return new IncidentNotifier(pager(), rota(), clock());
}
}
PagerProvider has multiple mixin-able implementations
interface EmailPagerProvider extends PagerProvider {
default Pager pager() { return new EmailPager("smtp.example.com"); }
}
interface ConsolePagerProvider extends PagerProvider {
default Pager pager() {
return (Person onCall, String message) ->
System.out.println("Stub pager says " + onCall + " " + message);
}
}
As I mentioned above, this pattern starts to add too much complexity for my liking, however neat it may be. Still, it can be a useful technique for using sparingly for parts of your application where manual constructor injection is becoming tedious.
Summary
There are various techniques for doing the kinds of things that we often use DI frameworks to do, just using pure Java. It’s worth considering the hidden costs of using the framework.
The code for the examples used in this post is on Github