I’m going to write a quick walthrough post on what is DI, what problems it is trying to solve, and how to manage it concisely in your project.

What does DI mean

In a previous blog post I discussed how I was moving the database backend from a H2 instance to a fully fledged Pgsql with connection pooling. However I was going to keep the H2 conf for testing, so I end up with a static Factory class that can provide the desired DataSource implementation: Factory::getPostgresDataSource and Factory::getH2DataSource.

This DataSource impl is used in the app code. Let’s assume for a moment that my app runs with a normal p.s.v. main() funcion in the App class. That is, we have something like this:

public class App {
    public static void main(String... args) {
        DataSource ds = Factory.getH2DataSource();
        // comment previous and uncomment next to switch to Postgres
        //DataSource ds = Factory.getPostgresDataSource();
        ...etc...
    }
}

In this case switching from a DataSource implementation to the other would not be such a pain after all.

However, things get complicated when it is not the frontend App class that uses the DataSource object, but some internal class. For example, a common pattern in enterprise Java is to have a Repository class that abstracts data manipulation for the frontend App class — there are several reasons why this makes sense, like for example the mantainability according to SOLID design. So let’s assume instead that App has a reference to Repository and that it is Repository that needs a DataSource instance. Something like:

public class App {
    public static void main(String... args) {
        Repository repo = new Repository();
        ...etc...
    }
}

public class Repository {
    private DataSource ds;
	public Repository() {
        ds = Factory.getH2DataSource();
        ...constructor...
    }
}

What happened now, is that the DataSource is not manageable anymore from the frontend. Granted, if we want to change the DataSource impl to be the Factory::getPostgresDataSource instead of the original, we can go to the Repository class and tweak the ds = …​ line. The problem is that if there are more other "middlemen" in the app structure other than Repository, that need to use a DataSource instance, things will go awry pretty soon: if by mistake we are using, say, DataSourceImpl1 in one part of the code and DataSourceImpl2 in another part of the code, what is going to happen?

So the only solution is to centralize somewhere the "configuration" of what DataSource implementation we want to use, say, in in the App class itself like we were doing originally, and to inject the chosen DataSource implementation from there into Repository. In other words: we have to change the Repository code so that the DataSource dependency is injected in the constructor by the caller, instead of being created over there:

public class App {
    public static void main(String... args) {
        DataSource ds = Factory.getH2DataSource();
        Repository repo = new Repository(ds);
        ...etc...
    }
}

public class Repository {
    private DataSource ds;
	public Repository(DataSource ds) {
		this.ds = ds;
        .. constructor...
    }
}

This is basically what Dependency Injection is all about: to make code more manageable, we centralize all chosen configuration of interfaces and inject it in cascade through the object constructors.

DI and Unit Testing

When working in structured manner (the aforementioned SOLID method), DI is very useful for unit testing. We can test separately pieces of code and mock the needed resources. In my case, I want that Repository uses the Postgres d.s. in the real world scenario, but the H2 d.s. in unittests. Unit testing then is relatively easy:

public class RepositoryTest {
    private DataSource ds;
    private Repository repo;

    @Before
    public void prepare() {
        ds = Factory.getH2DataSource();
    }

    @Test
    public void test1() {
        repo = new Repository(ds);
        ...test content...
    }

    ...other tests...
}

Of course it could be the case to totally create a database mock (e.g. using Mockito) instead of relying on H2 for testing. It does not matter though, because in the @Before function we would just write ds = mock(DataSource.class) instead of taking an implementation from Factory, but the injection of ds into Repository would still be done in the same way (the tests would not change…​ nice!).

Google Guice

Dependency Injection as a concept is not difficult to grasp. However, complex projects need to inject multitudes of configurations in multiple different places, and so DI easily becomes a burden.

Google Guice to the rescue! Guice is a DI framework oriented to simplicity and in-code configuration through the power of decorators. First, we need to add the dependency to the project pom.xml:

    <dependency>
        <groupId>com.google.inject</groupId>
        <artifactId>guice</artifactId>
        <version>4.1.0</version>
    </dependency>

Secondly, with Guice the DI configuration must be done within a class extending the abstract class AbstractModule provided by the framework itself. Let’s call this class AppConfig:

public class AppConfig extends AbstractModule {
    @Override
    protected void configure() {
        bind(DataSource.class).toInstance(Factory.getH2DataSource());
    }
}

The bind(this).to(that) DSL is very readable and intuitive: the this must be an interface, the that must be any implementation of that interface, that’s it!

More precisely, you have to master few different variants: if you get the implementation from a factory (like in our example), you have to use the .toInstance() DSL, but if you get the implementation from a written class, the correct syntax is .to().

There is a little other step to do in the actual App class code, and is to retrieve the configured implementation in AppConfig within the App class itself, using the Guice::createInjector function:

public class App {
    public static void main(String... args) {
        DataSource ds = Guice.createInjector(new AppConfig()).getInstance(DataSource.class);
        Repository repo = new Repository(ds);
        ...etc...
    }
}

So the net number of lines has slightly increased, but the DI configuration is now isolated in its own class. If we want to switch from H2 to Postgres, we just go to AppConfig and modify the line in configure() as bind(DataSource.class).toInstance(Factory.getPostgresDataSource()); without even touching the Application itself or its dependencies.

Needless to say, the very same approach can be done in unit testing: we can create an independent TestConfig class extending AbstractModule, and in the RepositoryTest discussed in the last section we would simply change the line in the @Before function like this:

    @Before
    public void prepare() {
        ds = Guice.createInjector(new TestConfig()).getInstance(DataSource.class);
    }

and the tests would still work as they did previously, no line of code should be touched other than this.

Foreword

You can see that DI is a very important piece of the puzzle in writing enterprise-quality code. In must be said though that it is a pattern coming from how Java (and C#) are structured and have traditionally been programmed — especially considering the evolution of those languagues and what became a pattern (DI) and what became an anti-pattern (not using DI when having multiple implementations of interfaces).

Other languages don’t have interfaces and in those kind of languages DI management is simply impossible (as also mocking is impossible). Some of these, like Python, have evolved in time and even if they don’t provide interfaces, they provide Abstract Base Classes (ABCs) as injecting point, so DI and mocking are possible.

Hope you liked my tutorial!