zio/zio2
ZLayer 를 이용한 dependency injection
wefree
2022. 7. 27. 20:05
문제
zio1 에서의 zlayer example 을 zio2 의 zlayer 를 이용해 구현해 보자
코드
아래처럼는 공식 가이드를 이용해 작성한 코드이지만.. 개인적으로 생각하는 코드는 바로 아래에 추가했다.
import zio._
import java.util.concurrent.TimeUnit
object ZLayerTest extends zio.ZIOAppDefault {
trait Logging {
def log(line: String): UIO[Unit]
}
object Logging {
def log(line: String): URIO[Logging, Unit] =
ZIO.serviceWithZIO[Logging](_.log(line))
}
case class LoggingLive(ref: Ref[Long]) extends Logging {
override def log(line: String): UIO[Unit] =
for {
current <- Clock.currentDateTime
count <- ref.modify(x => (x + 1, x + 1))
_ <- Console.printLine(current.toString + s"\t[$count]\t" + line).orDie
} yield ()
}
object LoggingLive {
val layer: ZLayer[Ref[Long], Nothing, LoggingLive] = ZLayer.fromFunction(LoggingLive.apply _)
// val layer: ZLayer[Ref[Long], Nothing, LoggingLive] =
// ZLayer {
// for {
// ref <- ZIO.service[Ref[Long]]
// } yield LoggingLive(ref)
// }
}
// passthrough 를 사용하면 R 부분을 그대로 유지하면서 A 쪽으로 정보를 그대로 넘길 수 있다.
// val layerPassthrough: ZLayer[Ref[Long], Nothing, Ref[Long] with LoggingLive] = LoggingLive.layer.passthrough
// launch = creates a ZIO that uses the services and never finishes
// val layerLaunch: ZIO[Ref[Long], Nothing, Nothing] = LoggingLive.layer.launch
// fresch
// val layerFresh = LoggingLive.layer.fresh
val program: ZIO[Logging, Nothing, Unit] = Logging.log("Hi") *> Logging.log("Hello")
val refLayer: ZLayer[Any, Nothing, Ref[Long]] = ZLayer(Ref.make(0L))
// val loggingZIO: ZIO[Any, Nothing, Logging] = LoggingLive.layer.provideSomeLayer(refLayer)
// 아래처럼 ZLayer.make() 를 이용해 따로 만들 수도 있다.
// val loggingLiveLayer: ZLayer[Any, Nothing, Logging] = ZLayer.make[Logging](LoggingLive.layer, refLayer)
// def run = program.provide(loggingLiveLayer)
// 개발할 때 ZLayer.Debug.mermaid 또는 ZLayer.Debug.tree 를 추가하면 tree 형태로 dependency 를 보여줌
def run = program.provide(LoggingLive.layer, refLayer, ZLayer.Debug.mermaid)
/*
Already provides services: Clock, Random, System, Console
*/
// val getTime = Clock.currentTime(TimeUnit.SECONDS)
// val randomValue = Random.nextInt
// val sysVariable = System.env("HADOOP_HOME")
}
공식 가이드는 위와 같이 case class 를 활용을 권장하지만, zlayer 로 변환 그리고 ZIO 의 R 과 결합되면서 매우 복잡해진다. 개인적으로는 아래처럼 코드를 작성하는 것이 더 심플하다고 생각한다. program 전반에 걸쳐 dependency 는 ZIO 의 R 로 관리하고, 마지막 결합에서 ZIO 를 Zlayer 로 변환해 provide 한다.
import zio._
object ZioApp extends zio.ZIOAppDefault {
trait Logging {
def log(line: String): UIO[Unit]
}
object Logging {
def log(line: String): URIO[Logging, Unit] = ZIO.serviceWithZIO(_.log(line))
}
val someLoggingZIO: ZIO[Ref[Long], Nothing, Logging] = for {
ref <- ZIO.service[Ref[Long]]
} yield new Logging {
override def log(line: String): UIO[Unit] =
for {
current <- Clock.currentDateTime
count <- ref.modify(x => (x + 1, x + 1))
_ <- Console
.printLine(current.toString + s"\t[$count]\t" + line)
.orDie
} yield ()
}
// val someLoggingZIO: ZIO[Ref[Long], Nothing, Logging] =
// ZIO.serviceWith[Ref[Long]] { ref =>
// new Logging {
// override def log(line: String): UIO[Unit] =
// for {
// current <- Clock.currentDateTime
// count <- ref.modify(x => (x + 1, x + 1))
// _ <- Console
// .printLine(current.toString + s"\t[$count]\t" + line)
// .orDie
// } yield ()
// }
// }
val program: ZIO[Logging, Nothing, Unit] =
Logging.log("Hi") *> Logging.log("Hello")
override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = {
val refLive: UIO[Ref[Long]] = Ref.make(0L)
val loggingLive: ZIO[Ref[Long], Nothing, Logging] = someLoggingZIO
val refLayer = ZLayer(refLive)
val loggingLayer = ZLayer(loggingLive)
program.provide(loggingLayer, refLayer, ZLayer.Debug.mermaid)
}
}
Scope 활용
Scope 를 이용한 resource 관리가 필요할 경우 ZLayer.scoped 를 사용한다.
import zio._
import java.io.IOException
import scala.io._
object ZLayerTest extends zio.ZIOAppDefault {
trait Show {
def print(line: String): Task[Unit]
}
object Show {
def print(line: String): ZIO[Show, Throwable, Unit] =
ZIO.serviceWithZIO(_.print(line))
}
case class ShowLive(source: Source) extends Show {
override def print(line: String): Task[Unit] =
Console.printLine(source.getLines().toList.headOption.getOrElse("") + "\n" + line)
}
object ShowLive {
val layer: ZLayer[Source, Nothing, ShowLive] = ZLayer.fromFunction(ShowLive.apply _)
}
val source: ZIO[Scope, IOException, Source] = {
def acquire(name: => String): ZIO[Any, IOException, Source] =
ZIO.attemptBlockingIO(Source.fromFile(name))
def release(source: => Source): ZIO[Any, Nothing, Unit] =
ZIO.succeedBlocking(source.close())
ZIO.acquireRelease(acquire("app.conf"))(release(_))
}
val program: ZIO[Show, Throwable, Unit] = Show.print("Show me the money")
// val sourceZIO: ZIO[Any, IOException, Source] = ZIO.scoped(source)
val sourceLayer: ZLayer[Any, IOException, Source] = ZLayer.scoped { source }
def run = program.provide(ShowLive.layer, sourceLayer)
}