Dependency Injection and Dependency Injection

The Play Framework implements the Guice Dependency Injection framework. Getting started with it has been a daunting process, one littered with errors. However, after a 3 weeks of using it now, I am starting to develop an understanding for its features.

One of the interesting takeaways was the intricacies surrounding the Guice Framework, which initially were not obvious to me. I think my initial confusion was around the @Inject tag. The first instance of needing to use the tag was confusingly during the writing of a test. The confusion in this case came because I still needed to pass in the item I wanted to inject.

@Inject
public CSVRepository(Environment env, String apprenticeCSV, String restaurantCSV, String scheduleCSV, String employeesCSV, String guestsCSV) {
    this.apprenticeCSV = env.getFile(apprenticeCSV).getAbsolutePath();
    this.scheduleCSV = env.getFile(scheduleCSV).getAbsolutePath();
    this.restaurantCSV = env.getFile(restaurantCSV).getAbsolutePath();
    this.employeesCSV = env.getFile(employeesCSV).getAbsolutePath();
    this.guestsCSV = env.getFile(guestsCSV).getAbsolutePath();
}

The constructor for our CSVRepository needed the Environment to be passed in, so that we could access the files used for storage. Without the inject tag in place, the tests would blow up. Despite an environment being passed in to the constructor.

    Storage storage = new CSVRepository(Environment.simple(), "test/resources/apprentices.csv", "test/resources/restaurants.csv", "test/resources/schedule.csv", "test/resources/employees.csv", "test/resources/guests.csv");

What is not clear from the guice error message, is that the failure is not really linked to the test, but to running the application in production. It is essentially a compilation error that is raised. So, the @Inject annotation really says is “find the Environment.class and inject an instance of it here”. The Guice/Play Framework pre-defines how some of the key classes should be instantiated for injection, they are annotated as such within the the framework source code.

Given that understand, it then become a question of how when injecting things into our controller, we should inform the framework what to use. It turns out there are a number of ways to do it. The key concept is that of a binding, and also providers. The Play Framework has a special configuration location within module files that extend the AbstractModule.

public class Module extends AbstractModule{

    @Override
    protected void configure() {
        bind(Storage.class).to(PostgresRepository.class);
    }

    @Provides
    CSVRepository provideCSVRepository() {
        CSVRepository csv = new CSVRepository(Environment.simple(), "conf/resources/apprentices.csv", "conf/resources/restaurants.csv", "conf/resources/schedule.csv", "conf/resources/employees.csv", "conf/resources/guests.csv");
        return csv;
    }
}

In the case shown above the @Provides signifies that this method should be implemented should a CSVRepository ever need to be injected. It knows this via the return type of the method.

The binding above that says that wherever Storage.class is listed to be injected, then a PostgresRepository should be injected in it’s place.

With those concepts in place, that covers the basic features of the Guice Injection framework. What these features lead to is what guice calls a dependency injection graph. So as one main item is injected, all the sub-classes and their sub-classes are individually instantiated and injected.

The benefits of using a framework like this are quite clean, and concise code. The alternatives to these injections are lots of factory methods which explicitly instantiate classes. Whilst the factory pattern is commonly used and understood, it is also another source of bugs and requires maintenance.