I am trying to learn about the cake pattern.
I am reading this blog about it.
The example code from that blog is:
case class User (name:String,email:String,supervisorId:Int,firstName:String,lastName:String)
trait UserRepository {
def get(id: Int): User
def find(username: String): User
}
trait UserRepositoryComponent {
def userRepository: UserRepository
trait UserRepository {
def get(id: Int): User
def find(username: String): User
}
}
trait Users {
this: UserRepositoryComponent =>
def getUser(id: Int): User = {
userRepository.get(id)
}
def findUser(username: String): User = {
userRepository.find(username)
}
}
trait UserInfo extends Users {
this: UserRepositoryComponent =>
def userEmail(id: Int): String = {
getUser(id).email
}
def userInfo(username: String): Map[String, String] = {
val user = findUser(username)
val boss = getUser(user.supervisorId)
Map(
"fullName" -> s"${user.firstName} ${user.lastName}",
"email" -> s"${user.email}",
"boss" -> s"${boss.firstName} ${boss.lastName}"
)
}
}
trait UserRepositoryComponentImpl extends UserRepositoryComponent {
def userRepository = new UserRepositoryImpl
class UserRepositoryImpl extends UserRepository {
def get(id: Int) = {
???
}
def find(username: String) = {
???
}
}
}
object UserInfoImpl extends
UserInfo with
UserRepositoryComponentImpl
I can simplify that code by removing Users
:
package simple {
case class User(name: String, email: String, supervisorId: Int, firstName: String, lastName: String)
trait UserRepository {
def get(id: Int): User
def find(username: String): User
}
trait UserRepositoryComponent {
def userRepository: UserRepository
trait UserRepository {
def get(id: Int): User
def find(username: String): User
}
}
trait UserInfo {
this: UserRepositoryComponent =>
def userEmail(id: Int): String = {
userRepository.get(id).email
}
def userInfo(username: String): Map[String, String] = {
val user = userRepository.find(username)
val boss = userRepository.get(user.supervisorId)
Map(
"fullName" -> s"${user.firstName} ${user.lastName}",
"email" -> s"${user.email}",
"boss" -> s"${boss.firstName} ${boss.lastName}"
)
}
}
trait UserRepositoryComponentImpl extends UserRepositoryComponent {
def userRepository = new UserRepositoryImpl
class UserRepositoryImpl extends UserRepository {
def get(id: Int) = {
???
}
def find(username: String) = {
???
}
}
}
object UserInfoImpl extends
UserInfo with
UserRepositoryComponentImpl
}
and it compiles just fine.
1) Why is the code in the blog so complicated ?
2) Is that the idiomatic way to use the cake pattern ?
3) Why is the Users
class needed in this example ?
4) Is that the way the cake pattern supposed to look like (with that seemingly unnecessary Users
class?
5) Or is the simplified version just fine ?
At first it might look like it's complicated, but once you get familiar with that pattern it's just... boilerplaty and cumbersome. For every service of yours you have to create an accompanying component that will be wrapping that service. So in the provided example you have a UserRepository
that's wrapped by a UserRepositoryComponent
. And this is only the abstraction, so you need to have a concrete implementation for both the component and the service (i.e. UserRepositoryComponentImpl
wrapping UserRepositoryImpl
). And so far you have only one service that might be used in your modules, imagine the effort to create dozens of services ;)
Yes, this is the idiomatic way to use that pattern. However there are also other variations of that pattern, e.g. thin cake pattern
or parfait
(a term coined by Dick Wall)
You're asking about User
, yet your code simplification was to remove Users
, so I'll describe both of them. User
is a simple case class, that should make that example more accessible/easier to grasp. Users
however were not necessary here (it's just another intermediate level of abstraction), and in my opinion they bring some unnecessary noise to the example.
I would say that your simplified version shows exactly how cake pattern should look like. You have an abstract UserRepository
wrapped inside of UserRepositoryComponent
, you have a concrete implementation of both those traits, and you have some service (UserInfo
) that requires the repository of users (which is "injected" using self-type annotation).
Already answered.