zio quick read: Extracting the executor for a service

zio quick read: Extracting the executor for a service

Let’s say you have the need to obtain an Executor from the default zio runtime and use it to create a service.

Let’s assume you have a simple function to make your service. This function has no zio knowledge.

def mkService(executor: java.util.concurrent.Executor): MyService = ???

Let’s say that you want to use the zio default runtime executor. For example, your service needs to use a specific Executor to power HttpClient–the new async http client in java 11. Otherwise, HttpClient uses a common, shared thread pool which may or may not be what you want.

The executor is in the ZIO environment and we want to create a layer so that MyService is in the environment R, the environment, for all effects in the program. We need to pull the executor out the environment and add the derived service as a layer.

Here’s the code (this is the hard way):

type MyEnv = Has[MyService]
val  resources: Layer[Nothing, MyEnv] = 
	ZLayer.fromEffect(ZIO.executor.map(_.asJava)) >>>
	ZLayer.requires[Has[java.util.concurrent.Executor]]
	.map(e => Has(mkService(e.get)))

The thinking is:

  • Pull the executor out using the ZIO.executor effect. Then add the “service” as a layer using ZLayer.fromEffect.
  • Vertically push the executor “service” into the next layer using >>>.
  • Use ZLayer.requires to create a layer that “requires” a Has[Exceutor] but map into it to create the final service. It’s messy because the map function must produce a Has service directly. We have to wrap it manually!

There is another way that’s easier.

We can use ZLayer.fromService to have zio pull out the service for us. ZLayer.fromService has implicit constraints that say that the Has[] services must exist as input into the function we provide it. ZLayer takes the object inside the Has[] and provides it as an input into our function. That is, fromService automatically unwraps the Has[] we need.

Since ZLayer.fromEffect creates a Has[] service from the ZIO.executor effect, the input for fromService comes from the left side of >>>. Output from fromService is automatically “wrapped” into a Has by zio. Nice!

val  resources: Layer[Nothing, MyEnv] = 
	ZLayer.fromEffect(ZIO.executor.map(_.asJava)) >>>
	ZLayer.fromService((e: java.util.concurrent.Executor) => mkService(e))
	
// even shorter...
val  resources: Layer[Nothing, MyEnv] = 
	ZLayer.fromEffect(ZIO.executor.map(_.asJava)) >>>
	ZLayer.fromService(mkService)

To use it, treat it like any other layer:

object Main extends zio.App:
  def run(args: List[String]) =
	program.provideCustomLayer(resources).exitCode

val program: ZIO[MyEnv, Nothing, Unit] = ???

Once you do this, all calls that may block the thread, say HttpClient.send vs HttpClient.sendAsync need to be enclosed in the proper blocking { ... } construct in ZIO. This will force the call to run on a special zio thread specifically reserved for blocking calls. This way, the main computation threads will not be blocked. Of course, if you are using the synchronous calls you may want to run them on the common thread pool executor anyway but that’s up to you.

That’s it!

Comments

Popular posts from this blog

zio layers and framework integration

typescript and react types

dotty+scala.js+async: interesting options