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
UserRepositorythat's wrapped by aUserRepositoryComponent. And this is only the abstraction, so you need to have a concrete implementation for both the component and the service (i.e.UserRepositoryComponentImplwrappingUserRepositoryImpl). 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 patternorparfait(a term coined by Dick Wall)You're asking about
User, yet your code simplification was to removeUsers, so I'll describe both of them.Useris a simple case class, that should make that example more accessible/easier to grasp.Usershowever 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
UserRepositorywrapped inside ofUserRepositoryComponent, 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.