My question is very similar to Scala Cake Pattern and Dependency Collisions. But I'm struggling to find a concrete solution as suggested in Daniel C's answer.
So here is the problem:
A ProductDetailsPage (trait) requires two independent service modules ProductServiceModule and SessionModule, implemented by ProductServiceModuleWs and SessionModuleWs respectively.
Both modules rely on a RestServiceConfigurationProvider.
For this RestServiceConfigurationProvider, there is only one concrete implementation available: DefaultRestServiceConfigurationProvider (atm).
The DefaultRestServiceConfigurationProvider on the other hand depends on theRestEndpointProvider which can either be a HybrisEndpointProvider or a ProductServiceEndpointProvider
In short, ProductServiceModuleWs and SessionModuleWs connect to remote RESTful web services. The exact IP address of the particular service is provided by an implementation of the RestEndpointProvider.
Now, this is where the collisions happens. Feel free to try out the code below. The troublesome place where the dependency collision happens is marked by a comment.
Rightfully so, the compiler complains about the two conflicting implementations of the RestEndpointProvider, namely HybrisEndpointProvider and ProductServiceEndpointProvider
As Daniel mentioned in his answer, to avoid any such collisions, I should wire up the ProductServiceModuleWs and SessionModuleWs separately, each with its own concrete RestEndpointProvider implementation, perhaps like so
new ProductServiceModuleWs
with DefaultRestServiceConfigurationProvider
with ProductServiceEndpointProvider
new SessionModuleWs
with DefaultRestServiceConfigurationProvider
with HybrisEndpointProvider
But here is where I got stuck.
How can those two separately configured modules be now injected into the ProductDetailsPage avoiding dependency collisions, but still utilizing the cake pattern?
Here is the example code. The code is self contained and should run in your IDE.
case class RestEndpoint(url: String, username: Option[String] = None, password: Option[String] = None)
trait RestEndpointKey {
def configurationKey: String
}
case object HybrisEndpointKey extends RestEndpointKey { val configurationKey = "rest.endpoint.hybris" }
case object ProductServiceEndpointKey extends RestEndpointKey { val configurationKey = "rest.endpoint.productservice" }
trait ProductDetailsPage {
self: ProductServiceModule with SessionModule =>
}
trait ProductServiceModule {}
trait SessionModule {}
trait ProductServiceModuleWs extends ProductServiceModule {
self: RestServiceConfigurationProvider =>
}
trait SessionModuleWs extends SessionModule {
self: RestServiceConfigurationProvider =>
}
trait RestServiceConfigurationProvider {}
trait DefaultRestServiceConfigurationProvider extends RestServiceConfigurationProvider {
self: RestEndpointProvider =>
}
sealed trait RestEndpointProvider {
def endpointKey: RestEndpointKey
}
trait HybrisEndpointProvider extends RestEndpointProvider {
val endpointKey = HybrisEndpointKey
}
trait ProductServiceEndpointProvider extends RestEndpointProvider {
val endpointKey = ProductServiceEndpointKey
}
object Example extends App {
new ProductDetailsPage
with ProductServiceModuleWs
with SessionModuleWs
with DefaultRestServiceConfigurationProvider
with HybrisEndpointProvider
with ProductServiceEndpointProvider /// collision, since HybrisEndpointProvider already defined the endpointKey !!!!!
}
}