Kevin Meredith Kevin Meredith - 13 days ago 8
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

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.

Comments