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 usingZLayer.fromEffect
. - Vertically push the executor “service” into the next layer using
>>>
. - Use
ZLayer.requires
to create a layer that “requires” aHas[Exceutor]
but map into it to create the final service. It’s messy because themap
function must produce aHas
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
Post a Comment