Kevin Meredith Kevin Meredith - 10 months ago 84
Scala Question

DI in Scala with Cake Pattern

This article explains Dependency Injection via Scala's

Cake Pattern
.

My understanding of this pattern's benefit is that traits can be mixed in (production v. test) with static checking.

In Mr. Bonér's example, he lists this finished (per example) code:

UserRepositoryComponent and UserServiceComponent

I added comments per my understanding.

trait UserRepositoryComponent {
val userRepository: UserRepository // stand-alone component

class UserRepository {
... // actual implementation here
}
}
trait UserServiceComponent {
this: UserRepositoryComponent => //Requires a mixed-in UserRepo*Component

val userService: UserService

class UserService {
... // actual implementation here
}
}


My understanding is that the
Service
depends on injection of a
Repository
component.

For production purposes, the following can be used to wire a "production"
Repository
component into the
UserServiceComponent
:

object ComponentRegistry extends
UserServiceComponent with
UserRepositoryComponent
{
val userRepository = new UserRepository
val userService = new UserService
}


If our production code wanted to use the
userRepository
or
userService
, is the correct way to use them via a simple
import
?

I think that I understand half of the article up to this point, but I'm not sure how to use the
ComponentRegistry
object.

Answer Source

You're running head first into the bakery of doom bro: What are some compelling use cases for dependent method types?

To answer your question, the proper way to use userService would be to use another trait and cake it up:

trait Example { this: UserServiceComponent => 
   def getExampleUser() = userService.getUser("ExampleUser")
}

Now whatever this new trait does isn't directly coupled to anything like the object ComponentRegistry. Instead your application becomes this:

object Application extends 
  Example with
  UserServiceComponent with 
  UserRepositoryComponent 
{
  val userRepository = new UserRepository
  val userService = new UserService
}

Anyway, you should run for the hills because if you really want to use cake you should be doing something more like this:

trait UserRepositoryComponent {

  type UserRepository <: UserRepositoryLike

  val userRepository: UserRepository

  trait UserRepositoryLike {
    def getUserOrSomething()
  }

}

trait UserRepositoryComponentImpl extends UserRepositoryComponent {

  type UserRepository = UserRepositoryImpl
  val userRepository = new UserRepositoryImpl

  class UserRepositoryImpl extends UserRepositoryLike {
    override def getUserOrSomething() = ???
  }

}

trait UserServiceComponent {
  this: UserRepositoryComponent =>

  type UserService <: UserServiceLike
  val userService: UserService

  trait UserServiceLike {
    def getUserNameById(id: Int): String
  }

}

trait UserServiceComponentImpl extends UserServiceComponent {
  this: UserRepositoryComponent =>

  type UserService = UserServiceImpl
  val userService = new UserServiceImpl

  class UserServiceImpl extends UserServiceLike {
    override def getUserNameById(id: Int) = userRepository.getUserOrSomething
  }

}

trait Example {
  this: UserServiceComponent =>

  def getExampleUser() = userService.getUserNameById(1)

}

object Application extends
Example with
UserRepositoryComponentImpl with
UserServiceComponentImpl

Now save yourself some time, drop the cake pattern, and do something simple.