zio
zio copied to clipboard
Layer overwrite possible bug
I'm not sure if this is expected behavior or I am missing something about how ZLayers work.
Below code prints:
bbbbb
bbbbb
I would expect it to print:
aaaaa
bbbbb
but somehow the 2nd layer overwrite the first ones, even though they are injected separately
object LayerTest extends ZIOAppDefault {
case class WrapperA(printer: Printer) {
def print:UIO[Unit] = printer.print
}
object WrapperA {
val layer = ZLayer.fromFunction(WrapperA(_))
}
case class WrapperB(printer: Printer) {
def print: UIO[Unit] = printer.print
}
object WrapperB {
val layer = ZLayer.fromFunction(WrapperB(_))
}
trait Printer {
def print: UIO[Unit]
}
case class PrinterLive(string: String) extends Printer {
override def print: UIO[Unit] = ZIO.succeed(println(string))
}
object PrinterLive {
val layer: ZLayer[String, Nothing, Printer] = ZLayer.fromFunction(PrinterLive(_))
}
val stringLayerA = ZLayer.succeed("aaaaa")
val stringLayerB = ZLayer.succeed("bbbbb")
override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = (for {
a <- ZIO.service[WrapperA]
b <- ZIO.service[WrapperB]
_ <- a.print
_ <- b.print
} yield ())
.provide(
stringLayerA >>> PrinterLive.layer >>> WrapperA.layer,
stringLayerB >>> PrinterLive.layer >>> WrapperB.layer
)
}
The fix is to change val
into def
object PrinterLive {
def layer: ZLayer[String, Nothing, Printer] = ZLayer.fromFunction(PrinterLive(_))
}
The more idiomatic solution is to use fresh, I think
val layer: ZLayer[String, Nothing, Printer] = ZLayer.fromFunction(PrinterLive(_)).fresh
If I remember correctly, by default ZLayer instances are shared. And fresh Creates a fresh version of this layer that will not be shared.
For any given type, you can have at most one service that implements that type. So layer sharing is on by default.
One workaround is to create a case class
wrapper that has the modified behavior you need for the subgraph of your application that needs it.