Guice Stories — Part 1

I’ve always been a fan of Google Guice. More so lately, as Guice became an indispensable part of Bootique.io project, responsible for Bootique’s dependency injection (DI) and modularity. Guice is a simple, fun and powerful DI engine with a number of advantages over the industry status quo, yet there’s not a lot of guidance out there on its proper use compared to, say, Spring. To partially fill this gap I wrote a few of “stories”, each describing a coding or a design task, then going through Guice solutions, from the most obvious to the most optimal. Hope this will be useful to developers who use either Guice or Bootique. This post starts with two stories. More will follow.

Story 1: Injection Hygiene

This story is about basic injection. We assume some services have been already “bound” in Guice and we want to use them to write our own “service”. The simplest way to get a hold of another service is via field injection:

public class A {    @Inject
private B b;
}

This is a quick and dirty approach. It is obvious why I am calling it quick: we declare an instance variable and annotate it with @Inject. Very little code. Why is it dirty though? Well, we ended up with a class that has a hard dependency on the injection container. There’s no way (short of ugly reflection) to create instances of A without Guice. So you won’t be able to write unit tests, etc. An obvious refactoring is to create a public constructor and use constructor injection:

public class A {private B b;    @Inject
public A(B b) {
this.b = b;
}
}

Much better — now both Guice and our own code will be able to create instances of A. This is good enough and we can stop here, or we can perfect our solution a bit more. Notice that we still have a compile dependency on the @Inject annotation. I don’t know about you, but annotations on domain objects always look like code smell to me. Let’s refactor this to a pure object, moving the construction code into a “provider” method inside our Guice module:

public class A {    private B b;    public A(B b) {
this.b = b;
}
}
public class MyModule implements Module { @Singleton
@Provides
A provideA(B b) {
return new A(b);
}
}

We ended up with a bit more code, but this code is arguably higher quality than what we had initially. And in a real app with many DI-managed objects of some complexity such a trade-off will actually pay off.

There are other reasons as well why constructor injection may not be ideal and the provider approach can save us. Some objects can have too many dependencies, so the argument list becomes unwieldy. (Yes, I am aware of setter injection, but that breaks object immutability, so let’s not go there). Also many objects require complex initialization logic. Using provider methods (or custom Provider classes) as a default choice keeps you from a temptation to stick factory code inside the constructor, and thus greatly reduces domain object coupling.

Story 2: Open/Closed Principle for Modules

I think the Injection story was pretty straightforward. Now let’s talk a bit about modules and collections. If you’ve been using Guice, you are likely familiar with Multibinder and MapBinder that allow us to declare injectable maps and collections. What does it really give us? Let’s consider an example. Say we have an imaginary Command interface, and also an imaginary CommandExecutor that looks up a command by name and then executes it:

public interface Command {
String exec();
}
public interface CommandExecutor {
String exec(String commandName);
}

Let’s define a few commands and write a provider method for our executor:

public class CommandA implements Command {
public String exec() {
return "a_result";
}
}
public class CommandB implements Command {
public String exec() {
return "b_result";
}
}
public class MyModule implements Module { @Provides
@Singleton
CommandExecutor provideCommandExecutor(
CommandA ca,
CommandB cb) {
Map<String, Command> commands = new HashMap<>();
commands.put("a", ca);
commands.put("b", cb);

return commandName -> commands.get(commandName).exec();
}
}

This code will work. There’s one problem with it though. The module that defines CommandExecutor will need to know all the commands upfront. This violates “open/closed principle”, or in simpler terms it does not allow us to create a reusable module with CommandExecutor, deferring command definitions to app-specific downstream modules. Changing this code to use MapBinder to collect commands cleanly solves the reusability problem:

public class ReusableModule implements Module {   public static MapBinder<String, Command> contributeCommands(
Binder binder) {
return MapBinder.newMapBinder(
binder, String.class, Command.class);
}
@Override
public void configure(Binder binder) {
// we don't have any commands in this module,
// but still need to call 'contribute*' once to ensure
// that an empty map is always available for injection.

contributeCommands(binder);
}
@Provides
@Singleton
CommandExecutor provideCommandExecutor(
Map<String, Command> commandMap) {
return name -> commandMap.get(name).exec();
}
}
public static class AppModule1 implements Module { @Override
public void configure(Binder binder) {
ReusableModule.contributeCommands(binder)
.addBinding("a")
.to(CommandA.class);
}
}
public static class AppModule2 implements Module { @Override
public void configure(Binder binder) {
ReusableModule.contributeCommands(binder)
.addBinding("b")
.to(CommandB.class);
}
}

Here we replaced a hardcoded map with an injectable map that is initially empty. Also we created a static contributeCommands method that gives other modules a hint on how our reusable module can be extended. So downstream modules can add any number of commands that will all be available to the application.

This all sounds simple, but it is a very important design pattern. It turns our DI environment into a powerful modularity engine. Common algorithms can be “bound” in generic reusable modules, but the actual set of objects they operate upon is composed by all collaborating modules present in the app. “contribute*” methods comprise custom Module-level API, often the only thing we need to know about any given Module.

Conclusion

This concludes Part 1 of Guice stories. It is not a substitute for Guice documentation, but will hopefully answer some practical questions and will get you over the learning curve faster. More stories will follow shortly.

Follow me on Twitter.

--

--

--

ObjectStyle ; Open Source: ApacheCayenne, Agrest, Bootique, and more…

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Gradle Tips & Tricks

OpenStack Network Setup

Some Basic Tips for Microsoft Excel

Stroke Prediction & Imbalanced Data

Day026 — Kiosk Mode

Growth Book 0.3.0 is Here 🚀

The top 10 frameworks to develop an efficient mobile app

A Better Experience for Sensu & Puppet Users

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Andrus Adamchik

Andrus Adamchik

ObjectStyle ; Open Source: ApacheCayenne, Agrest, Bootique, and more…

More from Medium

Exception handling

ANNOTATION IN JAVA :

Signed and Unsigned Arithmetic Operators in JAVA

Migration from Java EE to JakartaEE and Microprofile