zio/zio1

ZLayer module pattern 을 이용한 dependency injection

wefree 2021. 8. 18. 17:44

문제

zio module pattern 1.0 은 쓸데없이 복잡해 차라리 constructor dependency injection 을 쓰는게 낫다고 생각해 왔다. zio module pattern 2.0 이 최근에 소개되었는데, zio magic 과 함께 쓰면 어느정도 쓸만한 것 같다.

 

코드

import zio.clock.Clock
import zio.console.Console
import zio.magic._
import zio.{ App, ExitCode, Has, Ref, UIO, URIO, URLayer, ZIO, ZLayer }

object ZLayerTest extends App {
  trait Logging {
    def log(line: String): UIO[Unit]
  }

  object Logging {
    // URIO 가 아닌 ZStream 일 경우 ZIO.serviceWith 대신에
    // ZStream.accessStream(_.get.XXX) 사용
    def log(line: String): URIO[Has[Logging], Unit] = 
      ZIO.serviceWith[Logging](_.log(line))

    val refLayer: ZLayer[Any, Nothing, Has[Ref[Long]]] = Ref.make(0L).toLayer
    val fullLayer: URLayer[Has[Console.Service] with Has[Clock.Service] with Has[Ref[Long]], Has[Logging]] =
      LoggingLive.toLayer[Logging]

    val live: ZLayer[Has[Console.Service] with Has[Clock.Service], Nothing, Has[Logging]] =
      ZLayer.wireSome[Has[Console.Service] with Has[Clock.Service], Has[Logging]](refLayer, fullLayer)
  }

  case class LoggingLive(console: Console.Service, clock: Clock.Service, ref: Ref[Long]) extends Logging {
    override def log(line: String): UIO[Unit] =
      for {
        current <- clock.currentDateTime.orDie
        count   <- ref.modify(x => (x + 1, x + 1))
        _       <- console.putStrLn(current.toString + s": [$count] " + line).orDie
      } yield ()
  }

  override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = {
    val program: URIO[Has[Logging], Unit] = Logging.log("Hi") *> Logging.log("Hello")
    program.injectSome[zio.ZEnv](Logging.live).exitCode
  }
}

 

출력 결과 샘플

2021-08-19T01:10:28.234+09:00: [1] Hi
2021-08-19T01:10:28.257+09:00: [2] Hello