Home > Back-end >  Why override bootstrap, not work for run method?
Why override bootstrap, not work for run method?

Time:10-03

I'm learning ZIO 2.x, when configuring Runtime using bootstrap layer, it don't works.

object RuntimeCustom extends ZIOAppDefault {

  // It's not work, And I don't know why?
  override val bootstrap = EmailService.live
  
  def run = (for {
    _ <- ZIO.debug("Start...")
    _ <- EmailService.send("God", "Hi")
    _ <- ZIO.debug("End...")
  } yield ())
}

Get one error:

[error] /Users/changzhi/github-repo/zio-start/src/main/scala/zio/reference/experiment/core/RuntimeCustom.scala:35:7: 
[error] 
[error] ──── ZIO APP ERROR ───────────────────────────────────────────────────
[error] 
[error]  Your effect requires a service that is not in the environment.
[error]  Please provide a layer for the following type:
[error] 
[error]    1. example.EmailService
[error] 
[error]  Call your effect's provide method with the layers you need.
[error]  You can read more about layers and providing services here:
[error]  
[error]    https://zio.dev/next/datatypes/contextual/
[error] 
[error] ──────────────────────────────────────────────────────────────────────
[error] 
[error]     _ <- EmailService.send("God", "Hi")
[error]       ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed

If I replace with provide, it works.

object RuntimeCustom extends ZIOAppDefault {
  
  def run = (for {
    _ <- ZIO.debug("Start...")
    _ <- EmailService.send("God", "Hi")
    _ <- ZIO.debug("End...")
  } yield ())
    // This can works, have no doubt
    .provide(EmailService.live)
}

Full version program is here

package example

import zio._
trait EmailService {
  def send(user: String, content: String): Task[Unit]
}
object EmailService {
  def send(user: String, content: String): ZIO[EmailService, Throwable, Unit] =
    ZIO.serviceWithZIO[EmailService](_.send(user, content))

  val live: ZLayer[Any, Nothing, EmailService] = 
    ZLayer.fromZIO( ZIO.succeed(EmailServiceFake()) <* Console.printLine("Init EmailService") ).orDie
}

case class EmailServiceFake() extends EmailService {
  override def send(user: String, content: String): Task[Unit] =
    Console.printLine(s"sending email to $user")
}

object RuntimeCustom extends ZIOAppDefault {

  
  // It's not work, And I don't know why?
  //override val bootstrap = EmailService.live
  
  def run = (for {
    _ <- ZIO.debug("Start...")
    _ <- EmailService.send("God", "Hi")
    _ <- ZIO.debug("End...")
  } yield ())
    // This can works, have no doubt
    .provide(EmailService.live)
}

CodePudding user response:

ZIOAppDefault is meant for apps that will not require additional services, the assumption is that they will only "require" the built in services because you have provided away your other requirements with the provide method.

If you want to use the bootstrap method you should instead extend ZIOApp and override both the environmentTag and Environment fields so that the run mechanism knows how to construct the final environment.

import zio._

object App extends ZIOApp {
  
  // Tell ZIO how the environment is constructed
  override val environmentTag: EnvironmentTag[Environment] = EnvironmentTag[Environment]
  
  // Tell the app which layers will be leftover from the `run`
  override type Environment = FooService
  
  // The app how to construct those remaining layers
  override val bootstrap = FooService.layer
  
  
  val run = FooService.doFoo
  
  
}

class FooService {
  def doFoo: UIO[Unit] = ZIO.unit
}

object FooService {
  val layer = ZLayer.succeed(new FooService)
  
  def doFoo = ZIO.serviceWithZIO[FooService](_.doFoo)
}

Edit

Using the original example to show that this works:

import zio._


trait EmailService {
  def send(user: String, content: String): Task[Unit]
}
object EmailService {
  def send(user: String, content: String): ZIO[EmailService, Throwable, Unit] =
    ZIO.serviceWithZIO[EmailService](_.send(user, content))

  val live: ZLayer[Any, Nothing, EmailService] = 
    ZLayer.fromZIO( ZIO.succeed(EmailServiceFake()) <* Console.printLine("Init EmailService") ).orDie
}

case class EmailServiceFake() extends EmailService {
  override def send(user: String, content: String): Task[Unit] =
    Console.printLine(s"sending email to $user")
}

object RuntimeCustom extends ZIOApp {

  
  // It's not work, And I don't know why?
  override val bootstrap = EmailService.live 
  
  override type Environment = EmailService
  
  override val environmentTag: EnvironmentTag[Environment] = EnvironmentTag[Environment]
  
  
  def run = (for {
    _ <- ZIO.debug("Start...")
    _ <- EmailService.send("God", "Hi")
    _ <- ZIO.debug("End...")
  } yield ())
}

https://scastie.scala-lang.org/02MMaWS2S6Wwe55a24H7Sg

  • Related