Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection for `trait`

Given the following FooService:

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

There's also a MainService, which represents a main method.

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

FooService can have a fake (for testing) and a real implementation (hits DB, for example):

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

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

It seems to me that adding a "runner" class/trait, i.e. running sbt run would result in that class's execution, would be feasible. It would look like:

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 so sure that this is the idiomatic way of defining a real versus test MainService. Please let me know.

like image 991
Kevin Meredith Avatar asked Feb 11 '26 14:02

Kevin Meredith


1 Answers

You can use the popular cake pattern which is also known as a "Scala way" of doing dependency injection.

Jon did a great blog post about this with a walkthrough (he listed also some alternatives).

Firstly, the trait for the FooService:

trait FooServiceComponent {
  val fooService: FooService

  trait FooService {
    def go: Int
  }
}

That's saying, we need two things: 1. the actual object, and 2. its definition/implementation. Both namespaced together. Nice. Here are the Fake and Real versions:

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

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

Now, for the 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-typing this: FooServiceComponent which is a Scala way of saying that MainServiceComponent has a dependency on FooServiceComponent. If you try to instantiate MainServiceComponent without mixing in any FooServiceComponent then you will get a compile-time error. Nice. :)

Now, let's create the Test and Main objects with different traits:

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 because of namespacing, FakeService can't be accessed in Main because it's not mixed in. Nice. :) Note also that you delay any instantiation of class until this point, which is convenient in that you can easily use a registry or mocking library to substitute them all in one place.

Results:

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

I hope this helps.

like image 199
bjfletcher Avatar answered Feb 14 '26 07:02

bjfletcher



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!