Typesafe Activator

No-framework Dependency Injection with MacWire and Play Activator

No-framework Dependency Injection with MacWire and Play Activator

SoftwareMill
Source
December 10, 2014
basics playframework scala dependency-injection macros

This activator explains what Dependency Injection is and shows how to do DI in a Play application, with the help from a small library, MacWire. But without any additional frameworks! You will see how to divide the wiring of your classes into modules using traits, as well as how to use Scala Macros to remove some boilerplate code.

How to get "No-framework Dependency Injection with MacWire and Play Activator" on your computer

There are several ways to get this template.

Option 1: Choose macwire-activator in the Typesafe Activator UI.

Already have Typesafe Activator (get it here)? Launch the UI then search for macwire-activator in the list of templates.

Option 2: Download the macwire-activator project as a zip archive

If you haven't installed Activator, you can get the code by downloading the template bundle for macwire-activator.

  1. Download the Template Bundle for "No-framework Dependency Injection with MacWire and Play Activator"
  2. Extract the downloaded zip file to your system
  3. The bundle includes a small bootstrap script that can start Activator. To start Typesafe Activator's UI:

    In your File Explorer, navigate into the directory that the template was extracted to, right-click on the file named "activator.bat", then select "Open", and if prompted with a warning, click to continue:

    Or from a command line:

     C:\Users\typesafe\macwire-activator> activator ui 
    This will start Typesafe Activator and open this template in your browser.

Option 3: Create a macwire-activator project from the command line

If you have Typesafe Activator, use its command line mode to create a new project from this template. Type activator new PROJECTNAME macwire-activator on the command line.

Option 4: View the template source

The creator of this template maintains it at https://github.com/adamw/macwire-activator.

Option 5: Preview the tutorial below

We've included the text of this template's tutorial below, but it may work better if you view it inside Activator on your computer. Activator tutorials are often designed to be interactive.

Preview the tutorial

You've just created the Macwire + Play Framework example application! Let's explore what's inside.

Application overview

The main functionality of the application is to greet the user using a random greeting from an in-memory database. New greetings can be added by the user using a simple web form.

The task is quite simple and the code is probably a bit too complicated for such a simple functionality, but it should serve demo purposes well.

There are three main parts: the database module, the greetings module and the frontend part, consisting of controllers and the html pages. We'll explore each module in detail in the following sections, focusing on Dependency Injection, using traits as modules, MacWire and integration with the Play Framework.

To see how the application works, just visit http://localhost:9000 once the application has been compiled and the server started. Check Run to see the server status.

The database package

The main trait in the database package is Database. It defines a very simple interface for storing String key-pair values. It has a single implementation, InMemoryDatabase, which uses a ConcurrentHashMap for the actual storage.

Note that the InMemoryDatabase has a dependency: an AuditLogger. This implementation of the audit simply prints out information about each operation. A real-world implementation could, for example, use a RDBMS to implement the database, and write the audit to a file, including the current user's username with each entry.

Dependency Injection

The fact that InMemoryDatabase is dependent on AuditLogger is expressed using a constructor parameter. And this is the main idea behind Dependency Injection: instead of creating a specific implementation of an AuditLogger inside the InMemoryDatabase class using new, we only specify that we want "some" implementation. This decouples the two classes, as we can now use any AuditLogger implementation, without the need to change the code of the InMemoryDatabase class.

The removal of creational concerns from the code leads to less code pollution and increased testability. Very often usage of Dependency Injection is accompanied by a framework, such as Guice, Spring or Subcut. However, a framework is not required, and as this tutorial demonstrates, Scala language features can be used instead. Also, the mentioned frameworks most often rely on run-time reflection and hence provide less type safety. That said, they offer some advanced features which are not possible to achieve only with Scala/MacWire. For a more detailed comparision, please refer to the blogs mentioned at the end of the tutorial.

The database module

The database package also contains a DatabaseModule trait. In the trait we define how to create instances of the objects from this package.

In our case, and as is most common, creating the object graph is just calling new with the right parameters. Here we additionally constrain the type of the database value to be Database, so that clients do not see the underlying more specific implementation type.

Why lazy?

We use lazy vals, so that we don't have to worry about initialization order. When using vals objects have to be defined prior to usage. This can lead to weird NullPointerExceptions, hence if possible just use lazy vals or defs.

Trait-modules are optional

In smaller applications we could define the wiring (how the object graph is constructed) in one trait/object for all packages, however as the codebase size grows, it can become hard to manage, hence splitting into several "modules" may be a good idea. The cake pattern takes this idea even further, putting also class definitions into the traits.

Note that creating such traits is entirely optional and is not a part of the "core" Dependency Injection pattern. It is simply a way of using Scala's traits to make the code more manageable, and the pattern easier to use.

The greetings package

The classes in the greetings package are constructed in a similar way. They form a graph of dependencies (e.g. RandomGreetingsService depends on RandomItemChooser, and the GreetingsSaver depends on an instance of a Database.

The code is fairly simple so hopefully it is easy to understand what it does.

The greetings module

The GreetingsModule differs in two important ways from the DatabaseModule. Firstly, this module itself depends on the DatabaseModule, as we will need to access the database instance. Thanks to the way Scala traits work we can express module-level dependencies and combine several modules using trait composition.

MacWire

The second difference is in the way the object instances are defined: using the wire macro. A macro is a piece of Scala code which is executed at compile time, generating Scala code. The code is then type-checked and further compiled using standard rules. The wire macro is part of the MacWire library.

The wire macro will try to generate code to create a new instance of the given class, using as parameters values found in the current scope. For example,

wire[RandomGreetingsService]
will expand to
new RandomGreetingsService(database, randomItemChooser)
Note that this is all done at compile-time - there's no run-time component here! At run-time, all the JVM will see is new invocation. And we get compile-time checking that all dependencies of a class are satisfied!

Wiring using the macro can be useful when classes have several dependencies and enumerating all construction parameters would be tedious. Moreover the macro can be used only for some objects, others may be created by hand or using more complex custom code.

The controllers

There are two controllers. Unlike normal Play controllers, these are not objects, but classes, with some dependencies declared in constructors. Apart from that, the controllers are quite regular.

The first controller, MainController, is used to get a random greeting. The second controller, AddGreetingController, adds a new greeting to the database.

The frontend for the application is also quite standard, take a look at app/views and assets/javascripts/index.js if you are curious, but there's nothing Dependency Injection/MacWire-specific in these files.

Integrating the controllers

The controllers are wired using the approach we've seen before: see the Application object. The object depends on (extends) the two modules we defined earlier, hence when wiring the controllers, the necessary parameters are available.

The last missing piece in the puzzle is how to tell Play how to obtain instances of the controllers? Firstly, we have to reference controllers in the conf/routes file with the @ prefix, e.g.:

@controllers.MainController.index()
This will cause Play to delegate looking up instances of controllers to a special class.

That special class is Global. The method that is called by Play is getControllerInstance, passing a Class corresponding to the controller that needs to be looked up. Hence we need a map translating classes to instances.

Instance map

MacWire contains a utility macro to generate such a map: valsByClass(someObject). The macro will generate (again at compile-time) a map of all the vals in the given object, keyed by their classes. Finally, we are using the InstanceLookup helper class from MacWire, to be able to lookup instances taking into account super-classes and traits (e.g., our database instance will be keyed in the map generated by the macro by the InMemoryDatabase class, but we could want to look it up using the trait it implements, Database).

Testing

A very important aspect of our application is testing. One of the main benefits of Dependency Injection is the ease with which you can test components in isolation, passing in alternative implementations of any of the class dependencies.

As an example, take a look at GreetingsSaverSpec. Here we are using a mock Database, using a great mocking framework Mockito. Instead of providing a real database implementation, we only provide a mock instance, which records what calls where made to it. We can then verify that the desired interactions happened.

Integration testing

Using the trait-as-modules approach, it is also possible to integration-test whole packages, by instantiating the module trait. Alternative implementations can be then provided by overriding the appropriate vals.

The End

And that's it! We now have a simple Play application, which uses Dependency Injection for wiring the services and controllers.

The whole integration layer doesn't use reflection, only type-safe, compile-time code generation. And we're only using plain, old Scala traits and classes, without the help of any framework.

If you are interested, a couple of useful links:

comments powered by Disqus