Real-World Scala: dependency Injection (DI)

Update: Corrected typo in the Cake Pattern version of the last example.
Update: Added a version of the last example using the Cake Pattern for easier
comparison (see end of post).

In this second post in the Real-World Scala series I am going to discuss how to implement/achieve Depenency Injection (DI) in Scala. Scala is a very rich and deep language that gives you several ways of doing DI solely based on language constructs, but nothing prevents you from using existing Java DI frameworks, if that is preferred.

At Triental we tried out three different strategies before settling for the one we are using now. The plan for this article is as follows; first explain in detail how we are doing DI now, and then briefly cover the other alternative strategies we have tried out.

Using the Cake Pattern

The current strategy we are using is based on the so-called Cake Pattern. This pattern is first explained in Martin Oderskys’ paper Scalable Component Abstractions (which is an excellent paper that is highly recommended) as the way he and his team structured the Scala compiler. But rather than trying to explain the pattern and how it can be used to implement DI in plain English let’s take a look at some (naive) sample code (loosely based on our production code).

Note:
I will try to explain things in steps which I refactor towards the final version (this is only to help with the understanding), so please wait with yelling: “This sucks!”, until you have read and understood the final version (after which you are of course allowed come with any criticism/praise/suggestions/ideas you feel is necessary). Also, the sample code will, as in all these kind of examples, look like an insanely complicated way of doing almost nothing, but bare with me and try to envision real services in a large production system and how it applies there.

First, let’s create a UserRepository (DAO) implementation.

view plaincopy to clipboardprint?
  1. // a dummy service that is not persisting anything
  2. // solely prints out info
  3. class UserRepository {
  4. def authenticate(user: User): User = {
  5. println(«authenticating user: » + user)
  6. user
  7. }
  8. def create(user: User) = println(«creating user: » + user)
  9. def delete(user: User) = println(«deleting user: » + user)
  10. }

Here we could have split up the implementation in a trait interface and its implementation, but in order to keep things simple I didn’t see the need.

Now let’s create a user service (also a dummy one, merely redirecting to our repository).

view plaincopy to clipboardprint?
  1. class UserService {
  2. def authenticate(username: String, password: String): User =
  3. userRepository.authenticate(username, password)
  4. def create(username: String, password: String) =
  5. userRepository.create(new User(username, password))
  6. def delete(user: User) = All is statically typed.
  7. userRepository.delete(user)
  8. }

Here you can see that we are referencing an instance of the UserRepository. This is the dependency that we would like to have injected for us.

Ok. Now the interesting stuff starts. Let’s first wrap the UserRepository in an enclosing trait and instantiate the user repository there.

view plaincopy to clipboardprint?
  1. trait UserRepositoryComponent {
  2. val userRepository = new UserRepository
  3. class UserRepository {
  4. def authenticate(user: User): User = {
  5. println(«authenticating user: » + user)
  6. user
  7. }
  8. def create(user: User) = println(«creating user: » + user)
  9. def delete(user: User) = println(«deleting user: » + user)
  10. }
  11. }

This simply creates a component namespace for our repository. Why? Stay with me and I’ll show you how to make use of this namespace in a second.

Now let’s look at the UserService, the user of the repository. In order to declare that we would like to have the userRepository instance injected in the UserService we will first do what we did with the repository above; wrap the it in an enclosing (namespace) trait and use a so-called self-type annotation to declare our need for the UserRepository service. Sounds more complicated than it is. Let’s look at the code.

view plaincopy to clipboardprint?
  1. <br>
  2. // using self-type annotation declaring the dependencies this <br>
  3. // component requires, in our case the UserRepositoryComponent<br>
  4. trait UserServiceComponent { this: UserRepositoryComponent =><br>
  5. val userService = new UserService <br>
  6. class UserService {<br>
  7. def authenticate(username: String, password: String): User = <br>
  8. userRepository.authenticate(username, password) <br>
  9. def create(username: String, password: String) = <br>
  10. userRepository.create(new User(username, password))<br>
  11. def delete(user: User) = userRepository.delete(user)<br>
  12. }<br>
  13. }<br>

The self-type annotation we are talking about is this code snippet:

view plaincopy to clipboardprint?

1. this: UserRepositoryComponent =>

If you need to declare more than one dependency then you can compose the annotations like this:

view plaincopy to clipboardprint?

1. this: Foo with Bar with Baz =>

Ok. Now we have declared the UserRepository dependency. What is left is the actual wiring.

In order to do that the only thing we need to do is to merge/join the different namespaces into one single application (or module) namespace. This is done by creating a “module” object composed of all our components. When we do that all wiring is happening automatically.

view plaincopy to clipboardprint?
  1. object ComponentRegistry extends
  2. UserServiceComponent with
  3. UserRepositoryComponent

One of the beauties here is that all wiring is statically typed. For example, if we have a dependency declaration missing, if it is misspelled or something else is screwed up then we get a compilation error. This also makes it very fast.

Another beauty is that everything is immutable (all dependencies are declared as val).

In order to use the application we only need to get the “top-level” component from the registry, and all other dependencies are wired for us (similar to how Guice/Spring works).

view plaincopy to clipboardprint?
  1. val userService = ComponentRegistry.userService
  2. ...
  3. val user = userService.authenticate(..)

So far so good?

Well, no. This sucks.

We have strong coupling between the service implementation and its creation, the wiring configuration is scattered all over our code base; utterly inflexible.

Let’s fix it.

Instead of instantiating the services in their enclosing component trait, let’s change it to an abstract member field.

view plaincopy to clipboardprint?
  1. trait UserRepositoryComponent {
  2. val userRepository: UserRepository
  3. class UserRepository {
  4. ...
  5. }
  6. }
view plaincopy to clipboardprint?
  1. trait UserServiceComponent {
  2. this: UserRepositoryComponent =>
  3. val userService: UserService
  4. class UserService {
  5. ...
  6. }
  7. }

Now, we can move the instantiation (and configuration) of the services to the ComponentRegistry module.

view plaincopy to clipboardprint?
  1. object ComponentRegistry extends
  2. UserServiceComponent with
  3. UserRepositoryComponent
  4. {
  5. val userRepository = new UserRepository
  6. val userService = new UserService
  7. }

By doing this switch we have now abstracted away the actual component instantiation as well as the wiring into a single “configuration” object.

The neat thing is that we can here switch between different implementations of the services (if we had defined an interface trait and multiple implementations). But even more interestingly, we can create multiple “worlds” or “environments” by simply composing the traits in different combinations.

To show you what I mean, we’ll now create a “testing environment” to be used during unit testing.

Now, instead of instantiating the actual services we instead create mocks to each one of them. We also change the “world” to a trait (why, I will show you in a second).

view plaincopy to clipboardprint?
  1. trait TestingEnvironment extends
  2. UserServiceComponent with
  3. UserRepositoryComponent with
  4. org.specs.mock.JMocker
  5. {
  6. val userRepository = mock(classOf[UserRepository])
  7. val userService = mock(classOf[UserService])
  8. }

Here we are not merely creating mocks but the mocks we create are wired in as the declared dependencies wherever defined.

Ok, now comes the fun part. Let’s create a unit test in which we are mixing in the TestEnvironment mixin, which is holding all our mocks.

view plaincopy to clipboardprint?
  1. class UserServiceSuite extends TestNGSuite with TestEnvironment {
  2. @Test { val groups=Array(«unit») }
  3. def authenticateUser = {
  4. // create a fresh and clean (non-mock) UserService
  5. // (who's userRepository is still a mock)
  6. val userService = new UserService
  7. // record the mock invocation
  8. expect {
  9. val user = new User(«test», «test»)
  10. one(userRepository).authenticate(user) willReturn user
  11. }
  12. ... // test the authentication method
  13. }
  14. ...
  15. }

This pretty much sums it up and is just one example on how you can compose your components in the way you want.

Other alternatives

Let’s now take a look at some other ways of doing DI in Scala. This post is already pretty long and therefore I will only walk through the techniques very briefly, but it will hopefully be enough for you to understand how it is done. I have based all these remaining examples on the same little dummy program to make it easier to digest and to compare (taken from some discussion found on the Scala User mailing list). In all these examples you can just copy the code and run it in the Scala interpreter, in case you want to play with it.

Using structural typing

This technique using structural typing was posted by Jamie Webb on the Scala User mailing list some time ago. I like this approach; elegant, immutable, type-safe.

view plaincopy to clipboardprint?
  1. // =======================
  2. // service interfaces
  3. trait OnOffDevice {
  4. def on: Unit
  5. def off: Unit
  6. }
  7. trait SensorDevice {
  8. def isCoffeePresent: Boolean
  9. }
  10. // =======================
  11. // service implementations
  12. class Heater extends OnOffDevice {
  13. def on = println(«heater.on»)
  14. def off = println(«heater.off»)
  15. }
  16. class PotSensor extends SensorDevice {
  17. def isCoffeePresent = true
  18. }
  19. // =======================
  20. // service declaring two dependencies that it wants injected,
  21. // is using structural typing to declare its dependencies
  22. class Warmer(env: {
  23. val potSensor: SensorDevice
  24. val heater: OnOffDevice
  25. }) {
  26. def trigger = {
  27. if (env.potSensor.isCoffeePresent) env.heater.on
  28. else env.heater.off
  29. }
  30. }
  31. class Client(env : { val warmer: Warmer }) {
  32. env.warmer.trigger
  33. }
  34. // =======================
  35. // instantiate the services in a configuration module
  36. object Config {
  37. lazy val potSensor = new PotSensor
  38. lazy val heater = new Heater
  39. lazy val warmer = new Warmer(this) // this is where injection happens
  40. }
  41. new Client(Config)
Using implicit declarations

This approach is simple and straight-forward. But I don’t really like that the actual wiring (importing the implicit declarations) is scattered and tangled with the application code.

view plaincopy to clipboardprint?
  1. // =======================
  2. // service interfaces
  3. trait OnOffDevice {
  4. def on: Unit
  5. def off: Unit
  6. }
  7. trait SensorDevice {
  8. def isCoffeePresent: Boolean
  9. }
  10. // =======================
  11. // service implementations
  12. class Heater extends OnOffDevice {
  13. def on = println(«heater.on»)
  14. def off = println(«heater.off»)
  15. }
  16. class PotSensor extends SensorDevice {
  17. def isCoffeePresent = true
  18. }
  19. // =======================
  20. // service declaring two dependencies that it wants injected
  21. class Warmer(
  22. implicit val sensor: SensorDevice,
  23. implicit val onOff: OnOffDevice) {
  24. def trigger = {
  25. if (sensor.isCoffeePresent) onOff.on
  26. else onOff.off
  27. }
  28. }
  29. // =======================
  30. // instantiate the services in a module
  31. object Services {
  32. implicit val potSensor = new PotSensor
  33. implicit val heater = new Heater
  34. }
  35. // =======================
  36. // import the services into the current scope and the wiring
  37. // is done automatically using the implicits
  38. import Services._
  39. val warmer = new Warmer
  40. warmer.trigger
Using Google Guice

Scala works nicely with separate DI frameworks and early on we were using Google Guice. You can use Guice in many different ways, but here we will discuss a slick technique based on a ServiceInjector mixin that my Jan Kriesten showed me.

view plaincopy to clipboardprint?
  1. // =======================
  2. // service interfaces
  3. trait OnOffDevice {
  4. def on: Unit
  5. def off: Unit
  6. }
  7. trait SensorDevice {
  8. def isCoffeePresent: Boolean
  9. }
  10. trait IWarmer {
  11. def trigger
  12. }
  13. trait Client
  14. // =======================
  15. // service implementations
  16. class Heater extends OnOffDevice {
  17. def on = println(«heater.on»)
  18. def off = println(«heater.off»)
  19. }
  20. class PotSensor extends SensorDevice {
  21. def isCoffeePresent = true
  22. }
  23. class @Inject Warmer(
  24. val potSensor: SensorDevice,
  25. val heater: OnOffDevice)
  26. extends IWarmer {
  27. def trigger = {
  28. if (potSensor.isCoffeePresent) heater.on
  29. else heater.off
  30. }
  31. }
  32. // =======================
  33. // client
  34. class @Inject Client(val warmer: Warmer) extends Client {
  35. warmer.trigger
  36. }
  37. // =======================
  38. // Guice's configuration class that is defining the
  39. // interface-implementation bindings
  40. class DependencyModule extends Module {
  41. def configure(binder: Binder) = {
  42. binder.bind(classOf[OnOffDevice]).to(classOf[Heater])
  43. binder.bind(classOf[SensorDevice]).to(classOf[PotSensor])
  44. binder.bind(classOf[IWarmer]).to(classOf[Warmer])
  45. binder.bind(classOf[Client]).to(classOf[MyClient])
  46. }
  47. }
  48. // =======================
  49. // Usage: val bean = new Bean with ServiceInjector
  50. trait ServiceInjector {
  51. ServiceInjector.inject(this)
  52. }
  53. // helper companion object
  54. object ServiceInjector {
  55. private val injector = Guice.createInjector(
  56. Array[Module](new DependencyModule))
  57. def inject(obj: AnyRef) = injector.injectMembers(obj)
  58. }
  59. // =======================
  60. // mix-in the ServiceInjector trait to perform injection
  61. // upon instantiation
  62. val client = new MyClient with ServiceInjector
  63. println(client)

That sums up what I had planned to go through in this article. I hope that you have gained some insight in how one can do DI in Scala, either using language abstractions or a separate DI framework. What works best for you is up to your use-case, requirements and taste.

Update:

Below I have added a Cake Pattern version of the last example for easier comparison between the different DI strategies. Just a note, if you compare the different strategies using this naive example then the Cake Pattern might look a bit overly complicated with its nested (namespace) traits, but it really starts to shine when you have a less then trivial example with many components with more or less complex dependencies to manage.

view plaincopy to clipboardprint?
  1. // =======================
  2. // service interfaces
  3. trait OnOffDeviceComponent {
  4. val onOff: OnOffDevice
  5. trait OnOffDevice {
  6. def on: Unit
  7. def off: Unit
  8. }
  9. }
  10. trait SensorDeviceComponent {
  11. val sensor: SensorDevice
  12. trait SensorDevice {
  13. def isCoffeePresent: Boolean
  14. }
  15. }
  16. // =======================
  17. // service implementations
  18. trait OnOffDeviceComponentImpl extends OnOffDeviceComponent {
  19. class Heater extends OnOffDevice {
  20. def on = println(«heater.on»)
  21. def off = println(«heater.off»)
  22. }
  23. }
  24. trait SensorDeviceComponentImpl extends SensorDeviceComponent {
  25. class PotSensor extends SensorDevice {
  26. def isCoffeePresent = true
  27. }
  28. }
  29. // =======================
  30. // service declaring two dependencies that it wants injected
  31. trait WarmerComponentImpl {
  32. this: SensorDeviceComponent with OnOffDeviceComponent =>
  33. class Warmer {
  34. def trigger = {
  35. if (sensor.isCoffeePresent) onOff.on
  36. else onOff.off
  37. }
  38. }
  39. }
  40. // =======================
  41. // instantiate the services in a module
  42. object ComponentRegistry extends
  43. OnOffDeviceComponentImpl with
  44. SensorDeviceComponentImpl with
  45. WarmerComponentImpl {
  46. val onOff = new Heater
  47. val sensor = new PotSensor
  48. val warmer = new Warmer
  49. }
  50. // =======================
  51. val warmer = ComponentRegistry.warmer
  52. warmer.trigger
0 comments