The cake pattern is an important pattern to do dependency injection in Scala. It is explained in various blog posts on the web, for example by Jason Arhart, by Mark Harrison and by Eran Medan. And I am sure there are dozens of other places.
However, I want to write an explanation myself, mostly for my own benefit. But before we dive into the cake pattern, we first need to understand the so-called ‘self-type’ in Scala.
What is a Self-Type?
You might actually have seen it in Scala code before. A self-type is used to let a class state what type it will conform to when instantiated. This makes it not a whole lot clearer, so let’s run through an example.
For a job board we want to fetch all active vacancies. We can in this case create a VacancyService class:
1 2 3 4 5 |
class VacancyService() { this: VacancyRepository => def findActiveVacancies(): List[Vacancy] = all().filter { _.active } } |
Here you see the self-type in action. The VacancyService defines itself as ‘repository of type VacancyRepository ‘, which implements the findActiveVacancies method.
this can be replaced by self or another word you choose, which is something I like to make the code in VacancyService somewhat cleaner:
1 2 3 4 5 6 |
class VacancyService() { repository: VacancyRepository => def findActiveVacancies(): List[Vacancy] = repository.all().filter { _.active } } |
However, there is nothing here that shows that VacancyService really is of type VacancyRepository . When you want to use this class, you have to mix in the VacancyRepository behavior yourself:
1 |
val service = new VacancyService with MockVacancyRepository |
or:
1 |
val service = new VacancyService with JPAVacancyRepository |
Back to Dependency Injection
But as you saw in these last to code examples, we injected the necessary VacancyRepository implementation into the VacancyService .
Let us now complete the exmaple, with a mock repository. First we define the VacancyRepository with a mock implementation (you could add a JPA implementation yourself):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
trait VacancyRepository { def all(): List[Vacancy] def get(id: Int): Option[Vacancy] def save(user: Vacancy): Boolean } trait MockVacancyRepository extends VacancyRepository { private var users = List( Vacancy(1, true), Vacancy(2, false), Vacancy(3, true) ) def all: List[Vacancy] = users def get(id: Int): Option[Vacancy] = users.find { _.id == id } def save(user: Vacancy): Boolean = { users = user :: users true } } |
The Vacancy is defined as:
1 |
case class Vacancy(id: Int, active: Boolean) |
Now let’s create a VacancyService with a MockVacancyRepository . As a matter of fact, because VacancyRepository is abstract, we have to chose a specific implementation ( MockVacancyRepository) to let the code compile.
1 2 3 |
val service = new VacancyService with MockVacancyRepository service.findActiveVacancies() service.get(1) |
As for the word cake, we have now baked a service cake with two slices, the service and the repository.
Let’s conclude this post with a complete Scala worksheet example, that you can paste in in Scala IDE:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
object cakepattern { val service = new VacancyService with MockVacancyRepository //> service : VacancyService with MockVacancyRepository = cakepattern$$anonfun$m //| ain$1$$anon$1@20f5e794 service.findActiveVacancies() //> res0: List[Vacancy] = List(Vacancy(1,true), Vacancy(3,true)) service.get(1) //> res1: Option[Vacancy] = Some(Vacancy(1,true)) } class VacancyService() { repository: VacancyRepository => def findActiveVacancies(): List[Vacancy] = repository.all().filter { _.active } } trait VacancyRepository { def all(): List[Vacancy] def get(id: Int): Option[Vacancy] def save(user: Vacancy): Boolean } trait MockVacancyRepository extends VacancyRepository { private var users = List( Vacancy(1, true), Vacancy(2, false), Vacancy(3, true) ) def all: List[Vacancy] = users def get(id: Int): Option[Vacancy] = users.find { _.id == id } def save(user: Vacancy): Boolean = { users = user :: users true } } case class Vacancy(id: Int, active: Boolean) |
Writing this post made a lot of things clearer for me. If you have any additions, or want to show me where I am wrong, feel free to leave a comment.