-
ZLayer 를 이용한 dependency injectionzio/zio2 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) }
'zio > zio2' 카테고리의 다른 글
Ref vs Ref.Synchronized (0) 2022.12.09 Fiber (0) 2022.11.23 Error Models (0) 2022.11.20 Option 처리하기 (0) 2022.11.20 java callback 을 ZIO 로 바꾸기 (0) 2022.07.17