Guice in a Multi-Project Play! Environment: “Configuration of Functional Modules” Pattern

Léo Grimaldi
Keep It Up
Published in
3 min readJul 31, 2013

A few months ago, we explained how we used Guice with Play! for easy dependency injection, in order to make our code better organized and easier to test. More recently, we introduced separate multi-project deployment packages in our Play! Framework to make our code even more modular and easy to deploy. Breaking up our code into separate projects led us to rethink and formalize the way we organize our Guice bindings into Configuration and Functional Modules.

TL;DR

  • Keeping track of Guice’s Voodoo in a large Play! application with multiple sub-projects can be difficult.
  • Organize Guice bindings and provider methods into small non-overlapping functional modules.
  • Define a configuration module for each service that will install a pre-defined list of functional modules.

Problem: Coupling and override confusion from large Guice modules

Let’s reconsider the following Play! code architecture:

We used to write all our Guice provider methods in one central module for each service and mode:

In development mode, we have the ability to run services separately or altogether on the same JVM. In the latter case, combining service A and service B modules lead to Guice conflicts on common classes ClassC and ClassD. Resolving such conflicts requires to rely on overrides:

So does swapping implementations for a specific functionality in test:

Thus, while all provider definitions are gathered in one location (usually a pretty big file), this approach does not not scale as the number of services increases: more and more conflicts in test and development lead to confusing stacks of overrides. More importantly, as these modules can get quite large, tests that use a Play! application receive many more bindings than strictly required. These tests introduce coupling in the codebase. These became blocking as we were moving parts of our code to separate Play! projects.

Solution: Configuration/Functional Module Pattern

Here is our approach to maintain a central Guice configuration summary for each service, while allowing for easy combination of functional parts without relying on overrides.

We refer to each Guice module that defines concrete bindings and provider methods as a Functional Module. Each service application then installs one and only one ConfigurationModule in its global. A ConfigurationModule takes several functional modules and installs them with Guice’s “install” method:

Then you are free to define small functional modules that can be easily passed to the configuration module constructors:

Individual functional modules have logical unity and are usually derived in Prod/Dev/Test versions for one or multiple services. They can be composed as higher level functional module may install lower level functions. Note that we define functional modules as case classes for the following reasons:

  • If the same functional module is installed twice (from overlapping but non-conflicting submodules), Guice can rely on case class equality to understand that this is not a real conflict.
  • Case classes cannot be extended by other case classes, that should be an incentive to avoid provider method overrides. Instead, we just break up modules in smaller logical units.

Conclusion: Greater Code Modularity, Light Flexible Testing and Explicit Guice Configuration

As we do not rely on overrides, the Configuration/Functional pattern enforces us to maintain small functional modules. If a functional module is getting too large, we break it up into smaller logical submodules. This is the only way to avoid functional module overlaps, hence conflicts.

These small functional modules can further be combined on the spot to create light custom test environments (more to come in a future post). This enabled us to remove unnecessary injections in our test and development environment and move on towards a greater service split of our Play! project.

In the end, each given service (= one ConfigurationModule) or test (= one of a few test helpers covered in our next post) installs an explicit list of non-overlapping functional modules, so that we can better keep track of Guice’s Voodoo in the long run.

Originally published at eng.kifi.com on July 31, 2013.

--

--

Léo Grimaldi
Keep It Up

Engineering @YouTube. Former French Pastry Officer @Kifi @Stanford @CentraleParis