In all articles related to Cake patter that I found on the Internet I see a single level dependencies and it's clear to me.
But when I started using it I faced an issue that I can not use a service only in a high level class and I need to mix it in multiple places.
For example, if I have a service and this service works with a set of other services and each service in this set uses a database, I tried do not give a direct access to the database from this set of low level services. I made all database queries only in the high level service. But It is difficult in some cases.
May be the question will be more clear with the example:
trait DatabaseServiceComponent{
val databaseService: DatabaseService
trait DatabaseService{
def getSomeData(id: Int, tableName: String): List[String]
def getFriends(id: Int): List[Int]
}
}
trait DatabaseServiceComponentImpl extends DatabaseServiceComponent{
val databaseService: DatabaseService = new DatabaseServiceImpl
class DatabaseServiceImpl extends DatabaseService{
def getSomeData(id: Int, tableName: String): List[String] = ???
def getFriends(id: Int): List[Int] = ???
}
}
trait Scoring { this: DatabaseServiceComponent =>
def importantValues: Set[String]
val tableName: String
def getScore(id: Int): Double = databaseService.getSomeData(id, tableName).count(importantValues)
}
class Scoring1 extends Scoring{this: DatabaseServiceComponent =>
val tableName: String = "s1"
override def importantValues: Set[String] = Set("a", "b")
}
class Scoring2 extends Scoring{this: DatabaseServiceComponent =>
val tableName: String = "s2"
override def importantValues: Set[String] = Set("foo", "bar")
}
class Scoring3 extends Scoring{this: DatabaseServiceComponent =>
val tableName: String = "s3"
override def importantValues: Set[String] = Set("1", "2")
}
// How to implement this correctly?
trait Scoring2FriendsAverage {this: DatabaseServiceComponent =>
val scoring2: Scoring2
def getScore(id: Int):Double ={
val scores = databaseService.getFriends(id).map(scoring2.getScore)
scores.size / scores.sum
}
}
object FriendsScoringProcessor{
val scoring2Friends = new Scoring2FriendsAverage with DatabaseServiceComponentImpl{
val scoring2 = new Scoring2 with DatabaseServiceComponentImpl // I don't like that I have to mix the implementation of a service again
}
def printScores(id: Int): Unit = {
val score = scoring2Friends.getScore(id)
println(score)
}
}
I have a set of Scorings and each of them uses a database. I have a FriendsScoring which uses one of scoring which uses the database. I want to be able to mix database implementation only to the FriendsScoring and do not duplicate it in lower level services.
I see one good (may be) solution is to provide an implementation via an implicit constructor argument to low level service.
It looks like mixing levels of cake-pattern components and services.
If we use Scoring
at a service layer, then it shouldn't be present at cake-pattern level.
You might want to split Scoring
into two nested traits on each level like you did for Database:
trait ScoringComponent {this: DatabaseServiceComponent =>
trait ScoringService {
def getScore(id: Int): Double =
databaseService.getSomeData(id, tableName).
count(importantValues)
}
}
Then you'll be able to use ScoringService
after mixing the required dependencies.