No-framework Dependency Injection with MacWire and Akka Activator
Activator will be EOL-ed on May 24, 2017.
We’re making it easier and simpler for developers to get started with Lightbend technologies. This unfortunately means that future releases of Play, Akka and Scala will no longer include Activator support, and Lightbend’s Activator server will be decommissioned by the end of 2017. Instead of supporting Activator to create and set up development projects, we'll be supporting standard Giter8 templates for sbt users and Maven archetypes for Maven users. So going forward,
To create new Lightbend projects
Instead of using the Activator command, make sure you have sbt 0.13.13 (or higher), and use the “sbt new” command, providing the name of the template. For example, “$ sbt new akka/hello-akka.g8”. You can find a list of templates here.
Also, as a convenience, the Lightbend Project Starter allows you to quickly create a variety of example projects that you just unzip and run.
This activator explains what Dependency Injection is and shows how to do DI in an Akka 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.
The bundle includes a small bootstrap script that
can start Activator. To start Lightbend 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:
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 + Akka example application! Let's explore what's inside.
The functionality of this console application is to calculate a lucky number basing on the user's age and favorite color.
The task is quite simple and the code is definitely too complicated for such a simple functionality, but it should serve demo purposes well.
There are two main parts: the calculator module and the actors module. We'll explore each module in detail in the following sections, focusing on Dependency Injection, using traits as modules, MacWire and integration with Akka.
To see how the application works, just Run it! There's no fancy UI, unless you consider the console to be fancy :).
Let's start exploring the code (open the sidebar to read the tutorial along with the code)!
Note that the CalculateLuckyNumber has two dependencies: InterpretFavoriteColor and RandomModifier (as we all know, all lucky number computations are to some degree random). All of the classes are quite simple, and in the end we simply add up all the numbers we get.
The fact that CalculateLuckyNumber is dependent on InterpretFavoriteColor and RandomModifier is expressed using a constructor parameter. And this is the main idea behind Dependency Injection: instead of creating a specific implementation of e.g. an InterpretFavoriteColor inside the CalculateLuckyNumber class using new, we only specify that we want "some" implementation. This decouples the two classes, as we can now use anyInterpretFavoriteColor implementation, without the need to change the code of the CalculateLuckyNumber 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 or Spring. 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 comparison, please refer to the materials mentioned at the end of the tutorial.
The calculator module
The calculator package also contains a CalculatorModule trait. In the trait we define how to create instances of the objects from this package.
In many cases we can just call new with the right parameters to create the object graph. Note that even when calling new directly, we are doing Dependency Injection! The main point is that creating the objects (object graph) is done separately from defining the classes. Here however, we are using MacWire to remove a bit of the boilerplate that is associated with creating the instances.
The 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,
will expand to
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 calculator module #2
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 actors package
The actors package contains three actors which are responsible for accepting user requests, calculating the lucky number and emailing it back to the user (well, email support will be available in 2.0, here we are just printing to the console).
CalculateRequest is a simple data container which represents a user request. It is used by the CalculateActor to compute the lucky number and send it back to the sender. Note that the actor has a dependency - the service we defined earlier in the calculator package.
The EmailActor just prints outs any strings it get to the console. A much more interesting actor is the main one, EmailLuckyNumbersActor. That actor accepts requests, sends them to be calculated, and upon receiving a reply, emails the result.
There's a number of good ways to compose actors and pass around actor references; one quite widely used is sending the ActorRefs of collaborating actors in messages. Here, we are using another approach: passing the ActorRef as constructor parameters (dependencies). Both styles can be mixed of course, depending on the specific use-case.
If you take a closer look at EmailLuckyNumbersActor, you'll notice that the dependencies have "tags", ActorRef @@ Calculate and ActorRef @@ Email. The @@ T part comes from MacWire, and is entirerly optional, but it helps to differentiate which actor reference is which and gives some additional compile-time type checks; we can only use properly tagged instances when passing parameters around.
The tags have no runtime overhead and the original idea comes from Miles Sabin and is also used e.g. in Scalaz.
Tags can be added to an instance using the .taggedWith[T] method, which is available after importing com.softwaremill.macwire._. For example, we could tag an existing actor reference like this:
The tags are usually simple
The actors module
Just as with the calculator module, there's also a trait-module for actors, the ActorsModule.
To create actors, first of all we need to have access to a ActorSystem. The module has an abstract value of that type, which expresses a dependency of the module (which is a "level higher" than a dependency of a single class). It also has another dependency, on a CalculateLuckyNumber. Any usages of that module will have to provide an implementation of these two values, and this will be enforced by the compiler.
Secondly, creating actors is a bit different then creating normal objects, as we have to wrap the actor-instance creation code with actorSystem.actorOf(Props(...)). As actors should be created with care, and we probably only want them to be created explicitly, we are using def create...() methods, instead of values.
Note that we are tagging the email/calculate actors appropriately, and that creating the EmailLuckyNumbersActor requires references to other actors being passed in. These actor references from method parameters will be used when creating a EmailLuckyNumbersActor instance.
The LuckyNumberDemo main class wraps all that we have done so far together.
First of all, we extend both modules we have defined: CalculatorModule and ActorsModule. This brings into scope all of the values and methods defined. Note that also the EmailLuckyNumbersActor dependency from ActorsModule is automatically satisfied by the definition in CalculatorModule (and of course, all of that is checked during compilation, using standard Scala mechanisms)! The actor system dependency is not defined in any module hence we have to define it.
Secondly, we create the actors explicitly, passing around the right parameters. Thanks to tagging, we won't be able to mix the email and calculator actor references when passing them to createEmailLuckyNumbersActor.
Finally, we calculate the result for some example data using our actor infrastructure!
Try yourself - just Run the code and enjoy dependency injection with plain Scala, Akka and MacWire!
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 CalculateLuckyNumberTest. To be able to verify the behavior of the calculator, we need to substitute the random modifier which something that isn't random, e.g. returns a constant number.
Nothing easier! We just override the value from the module, and it will be used when wiring an instance of CalculateLuckyNumber!
What we have created is in fact an integration test of the whole module. We could have also created a new CalculateLuckyNumber instance directly and pass in either real or mock dependencies.
And that's it! We now have a simple Akka application, which uses Dependency Injection for wiring the services and actors.
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.