I'm currently baking my first cake pattern, so please bear with me.
I took my working monolithic app and I cutted it into functional layers. The cut looks clean but resulted in two of the layers that depend on an implicit ActorSystem.
I tried to solve this dependency like this:
trait LayerA {
this: ActorSystemProvider =>
private implicit val implicitActorSystem = actorSystem
import implicitActorSystem.dispatcher // implicit execution ctx
...
}
... and similarly for LayerX
My assembly class looks like:
class Assembly extends LayerA with LayerB with LayerX with ActorSystemProvider
where ActorSystemProvider simply instantiates the actor system.
This does not work given that the ActorSystem
does not exist when the dependencies are resolved and the val's are instantiated, resulting in a NPE. This also looks really ugly and I'm sure there has to be a nicer/easier way to deal with it.
How should I deal with shared implicit dependencies among layers when using the cake pattern, like ActorSystem
in this case?
Thanks
Self types is not a requirement for building a caked architecture, actually i use self types only in cases when a trait is a component of a layer. So when i need to place some implicit into the scope (for example ActorRefFactory for Spray Client) i just mix a trait in :
trait ActorSystemProvider {
implicit def actorSystem: ActorSystem
}
And on the lowest layer (so called "end of the world") i have the following code structure:
trait ServiceStack
extends SomeModule
with SomeModule2
with SomeModule3
with ActorSystemProvider
object ServiceLauncher extends App with ServiceStack {
val actorSystem = ActorSystem("ServiceName")
}
It's an oversimplified example (if you want a great example of a real system build on top of a Cake Pattern then you should definitely take a look at the Precog system, example where different modules/layers connects), but not you can mix implicit ActorSystem when you need it.
If you can instantiate the vals lazily rather than eagerly, you can make the implicitActorSystem a lazy val instead of a val. So it only gets executed when it is accessed the first time. I think this should solve the problem of NPE. (Another little known interesting fact posted by @ViktorKlang FYI: If the initialization of a lazy val throws an exception, it will attempt to reinitialize the val at next access.)
Another way would be to make each of your methods which need execution context accept an implicit executionContext like:
trait LayerA {
def getUser(id: Int)(implicit ec: ExecutionContext) = {
...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With