scalaadvanced

ZIO Layers and Dependency Management

Use ZIO ZLayer for dependency injection: service definitions, layer composition, and testing.

scala
import zio.*

// Service definitions
trait UserRepo:
  def findById(id: Long): Task[Option[String]]
  def save(name: String): Task[Long]

trait EmailService:
  def send(to: String, subject: String, body: String): Task[Unit]

trait UserService:
  def register(name: String, email: String): Task[Long]
  def getUser(id: Long): Task[Option[String]]

// Implementations
case class UserRepoLive() extends UserRepo:
  private var users = scala.collection.mutable.Map(1L -> "Alice")
  private var nextId = 2L

  def findById(id: Long): Task[Option[String]] =
    ZIO.succeed(users.get(id))

  def save(name: String): Task[Long] = ZIO.succeed {
    val id = nextId; nextId += 1
    users(id) = name; id
  }

case class EmailServiceLive() extends EmailService:
  def send(to: String, subject: String, body: String): Task[Unit] =
    ZIO.succeed(println(s"  Email to $to: $subject"))

case class UserServiceLive(repo: UserRepo, email: EmailService) extends UserService:
  def register(name: String, emailAddr: String): Task[Long] =
    for
      id <- repo.save(name)
      _  <- email.send(emailAddr, "Welcome", s"Hello $name, ID=$id")
    yield id

  def getUser(id: Long): Task[Option[String]] =
    repo.findById(id)

// Layers
object UserRepo:
  val live: ZLayer[Any, Nothing, UserRepo] =
    ZLayer.succeed(UserRepoLive())

object EmailService:
  val live: ZLayer[Any, Nothing, EmailService] =
    ZLayer.succeed(EmailServiceLive())

  // Test implementation
  val test: ZLayer[Any, Nothing, EmailService] =
    ZLayer.succeed(new EmailService:
      def send(to: String, subject: String, body: String): Task[Unit] =
        ZIO.succeed(println(s"  [TEST] Would send to $to: $subject"))
    )

object UserService:
  val live: ZLayer[UserRepo & EmailService, Nothing, UserService] =
    ZLayer {
      for
        repo  <- ZIO.service[UserRepo]
        email <- ZIO.service[EmailService]
      yield UserServiceLive(repo, email)
    }

object MyApp extends ZIOAppDefault:
  val program: ZIO[UserService, Throwable, Unit] = for
    service <- ZIO.service[UserService]
    id      <- service.register("Bob", "bob@test.com")
    _       <- Console.printLine(s"Registered with ID: $id")
    user    <- service.getUser(id)
    _       <- Console.printLine(s"Found: $user")
    _       <- Console.printLine(s"Missing: ${service.getUser(99)}")
  yield ()

  // Compose layers
  val appLayer: ZLayer[Any, Nothing, UserService] =
    (UserRepo.live ++ EmailService.live) >>> UserService.live

  // For testing
  val testLayer: ZLayer[Any, Nothing, UserService] =
    (UserRepo.live ++ EmailService.test) >>> UserService.live

  def run = program.provide(appLayer)

Use Cases

  • Dependency injection with ZIO
  • Testable service architecture
  • Layer composition for modular apps

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.