How to define an http4s server as a ZIO ZLayer to be injected and fetched in the main?

783 views Asked by At

Help me define an http4s using ZLayers. I'm learning and I'm confused. I'd like to factor out the http server as a component. But I don't know how to compose the ZManageds and the the ZLayers so that it would compile.

Also does it makes sense to create a layer that requires a Runtime[ZEnv]? Or does that make more sense to create a layer that requires a ZEnv and generate the runtime for it.

object HttpServer {

  def createHttp4Server: ZManaged[Runtime[ZEnv], Throwable, Server] = 
      ZManaged.accessManaged { implicit runtime: Runtime[ZEnv] =>
        BlazeServerBuilder[Task](runtime.platform.executor.asEC)
          .bindHttp(8080, "localhost")
          .withHttpApp(Routes.helloWorldService)
          .resource
          .toManagedZIO
      }


  def createHttp4Layer: ZLayer[RuntimeLayer, Throwable, Http4ServerLayer] =
    ZLayer.succeed(createHttp4Server)

}

object Routes {
  val helloWorldService = ...
}
package object simple {
  type Http4ServerLayer = Has[ZManaged[Runtime[ZEnv], Throwable, Server]]
  type RuntimeLayer = Has[Runtime[ZEnv]]
}

I don't know how to access the ZManaged[..., ..., Server] from the Layer in here in the main. I don't understand the access methods completely.

object ItsAren extends App {

  def run(args: List[String]): URIO[ZEnv, ExitCode] = {

    val toProvide: ZIO[Http4ServerLayer, Nothing, ExitCode] =
      ZIO
        .accessM[Http4ServerLayer](_.get.useForever) //compile error
        .as(ExitCode.success)
        .catchAll(ZIO.succeed(ExitCode.failure))

    val runtimeLayer: ZLayer[Any, Nothing, RuntimeLayer]              = ZLayer.succeed(Runtime.default)
    val http4Layer: ZLayer[RuntimeLayer, Throwable, Http4ServerLayer] = HttpServer.createHttp4Layer
    val fullLayer: ZLayer[Any, Throwable, Http4ServerLayer]           = runtimeLayer >>> http4Layer

    val provided = toProvide.provideCustomLayer(fullLayer)

    provided //compile error
  }

}
type mismatch
 found   : ZIO[Runtime[ZEnv], Throwable, Nothing]
 required: ZIO[Http4ServerLayer, ?, ?]

also at the bottom, but that's lower importance

 found   : ZIO[ZEnv, Throwable, ExitCode]
 required: URIO[ZEnv, ExitCode]

Same thing in a PR, feel free to comment https://github.com/kovacshuni/itsaren/pull/1

1

There are 1 answers

0
Hunor Kovács On

Solution is that a ZLayer when holding a Managed, is a Managed itself. So when something is running on a layer like this, the included Managed will be used through that time. So if you run a ZIO.never on a layer, the server will be kept alive.

To build the layer itself was another trick to use fromManaged. And the createHttp4Server requires a ZEnv only, no point in requiring a Runtime I admit now. ZManaged.runtime is another trick they have for not to use ZManaged.accessManaged { implicit runtime: Runtime[ZEnv] =>.

Full solution https://github.com/kovacshuni/zio-http4s-zlayer-example

object Main extends App {

  def run(args: List[String]): URIO[ZEnv, ExitCode] = {

    val program: ZIO[Has[Server] with Console, Nothing, Nothing] =
      ZIO.never

    val httpServerLayer: ZLayer[ZEnv, Throwable, Http4Server] = Http4Server.createHttp4sLayer

    program
      .provideLayer(httpServerLayer ++ Console.live)
      .exitCode
  }

}
import zio._
import zio.interop.catz._
import zio.interop.catz.implicits._

import org.http4s.server.Server
import org.http4s.server.blaze.BlazeServerBuilder

object Http4Server {

  type Http4Server = Has[Server]

  def createHttp4Server: ZManaged[ZEnv, Throwable, Server] =
    ZManaged.runtime[ZEnv].flatMap { implicit runtime: Runtime[ZEnv] =>
      BlazeServerBuilder[Task](runtime.platform.executor.asEC)
        .bindHttp(8080, "localhost")
        .withHttpApp(Routes.helloWorldService)
        .resource
        .toManagedZIO
    }

  def createHttp4sLayer: ZLayer[ZEnv, Throwable, Http4Server] =
    ZLayer.fromManaged(createHttp4Server)

}