Dependency injection for `trait`

Considering the following FooService

:

scala> trait FooService { 
     |   def go: Int 
     | }
defined trait FooService

      

There also exists MainService

which is the main method.

scala> trait MainService extends FooService { 
     |   def f = go + 42
     | }
defined trait MainService

      

FooService

may have a fake (for testing) and a real implementation (for example, a database):

scala> object FakeService extends FooService { 
     |   def go = 10
     | }
defined object FakeService

scala> object RealService extends FooService {
     |   def go = 55 // in reality, let say it hit the DB and got a value
     | }
defined object RealService

      

It seems to me that adding a class / trait "runner", i.e. execution sbt run

will result in the execution of this class. It will look like this:

scala> class Main extends MainService {
     |   override def go = RealService.go
     | }
defined class Main

      

And I could define a test too:

scala> class Test extends MainService {
     |   override def go = FakeService.go
     | }
defined class Test

      

I'm not sure if this is the idiomatic way of defining a real test case MainService

. Please let me know.

+3


source to share


1 answer


You can use the popular cake pattern also known as the "Scala way" for dependency injection.

John made a great blog post about this with a step by step guide (he also lists some alternatives).

First, a trait for FooService

:

trait FooServiceComponent {
  val fooService: FooService

  trait FooService {
    def go: Int
  }
}

      

In this statement, we need two things: 1. the actual object and 2. its definition / implementation. Both named together. Nice. Here are the versions Fake

and Real

:

trait FakeService extends FooServiceComponent {
  class FakeService extends FooService {
    def go = 10
  }
}

trait RealService extends FooServiceComponent {
  class RealService extends FooService {
    def go = 55
  }
}

      

Now, for MainService

:

trait MainServiceComponent { this: FooServiceComponent =>
  val mainService: MainService

  class MainService extends FooService {
    def f = go + 42
    def go = fooService.go // using fooService
  }
}

      



Note the self-tuning this: FooServiceComponent

, which is Scala's way of saying it MainServiceComponent

has a dependency on FooServiceComponent

. If you try to instantiate MainServiceComponent

without mixing anywhere FooServiceComponent

, then you get a compile time error. Nice.:)

Now create objects Test

and Main

with different features:

object Test extends MainServiceComponent with FakeService {
  val mainService = new MainService()
  val fooService = new FakeService()
}

object Main extends MainServiceComponent with RealService {
  val mainService = new MainService()
  val fooService = new RealService()
}

      

Note that due to the namespace, FakeService

it is not possible to access Main

it because it has not been shuffled. Nice. :) Note also that you are delaying any class creation until this point, which is handy in that you can easily use the registry or a mocking library to replace them all in one place.

Results:

println(Test.mainService.f) // -> 52
println(Main.mainService.f) // -> 97

      

Hope this helps.

+4


source







All Articles