5 (more) pitfalls to avoid when starting to work with ZIO

Natan Silnitsky
Jan 17 · 6 min read

My first ZIO post - 5 pitfalls to avoid when starting to work with ZIO was well received and I even got encouragement by some of my colleagues at Wix to share more of my beginner experiences with ZIO. So I’ve decided to write a sequel (like any current Hollywood screenwriter is easily persuaded to do).

If you are unfamiliar with pure functional programming and ZIO, you can find some useful links at the intro of my previous post

Image for post
Image for post
Photo by Ronaldo de Oliveira on Unsplash

All the following pitfalls are again from my first-hand experience with writing ZIO code, stumbling a bit here and there, and making a note of it. I hope these notes encourage you to learn more about how to write code with ZIO and avoid some of my mistakes.

1. Calling future inside

When you start writing in ZIO, you have a lot of legacy APIs you are required to wrap or “lift” into. The most common wrapper is which allows you to wrap synchronous code which has side-effects:

The problem starts when you don’t notice that you are “lifting” an asynchronous method that returns .

For example, let’s say you are writing code for a service that processes a cart checkout request (See below). There are two client calls. The first call is to fetch the cart details, and the second call is to execute payment.

method is synchronous:
We assume (wrongly) that the method is also synchronous.

We don’t care about the type of the result, just that it completes successfully, and we know that any exceptions would be handled because returns a in the error channel, which will make the service endpoint return a failure. Thus we decide to discard the pay result:

The compiler is happy, so we are happy, right? Wrong!

The actual signature of the pay method is:

Meaning that ZIO is going to put in the success channel a, but what we really want to be returned is . The async action executed by is going to be invoked on a separate thread (outside the ZIO thread pool!) and the ZIO effect will complete without even waiting for the underlying async operation to complete.

For instance, the operation could complete with Failure and still our service endpoint will return a successful response (because is just a benign success value).

The way to solve this is simply to change the wrapping method from to :

.

Now our ZIO effect will complete only once the Future action does and any Future failure will be propagated to the ZIO error channel.

2. Combining Eager and Lazy code inside

Any Scala developer is extremely accustomed to sprinkle their code with statements when debugging async code.

But this can be problematic when writing complexstatements.
For instance, the following examples starts up drivers in test environment:

Now, let’s say there is some weird failure and we decide to add logs to debug:

Notice how we unintentionally added a statement in line 3 without wrapping it in ZIO. the standard output for this app would be:

Starting to build mysql driver
Starting to build redis driver
>>>> going to create drivers
>>>> created mysql driver
>>>> started mysql driver
>>>> created redis driver

Notice how the prints are out of order because we forgot to wrap a few statements with . This can become very confusing for more complex cases…

The easiest way to avoid such issues is to use instead of . This way, the compiler will not allow you to make this mistake.
And in general - try to have all the code wrapped inside effects.

3. Wrapping pattern matching (or conditional) expressions with inside a for comprehension

For comprehensions with can be quite tricky for beginners, especially when they contain pattern matching expressions.

The example below shows a pattern match expression wrapped in

The entire pattern match expression was wrapped in ZIO because the and API is legacy:

But, there is a problem here, we missed the fact that the API was changed to return ZIO:

So now, only the User API is still functioning correctly, while the Media API is not, because it is wrapped in ZIO twice! (Once by the API, and the second time by the around the pattern match, giving us which is unusable)

The lesson here is to never blindly wrap whole pattern match expressions in ZIO, instead each should be evaluated separately and wrapped if needed:

4. Throwing FiberFailure instead of underlying Throwable

ZIO has great error tracing capabilities. When executing ZIO with , a will be thrown that will include the useful information of the next LoC to run if the error did not happen:

Fiber:Id(1576169440339,10) was supposed to continue to:
a future continuation at com.demo.CheckoutService.
processCheckout(CheckoutService.scala:60)

Sometimes, for inter-op purposes we need our method to throw the underlying and NOT Usually because the client code expects to catch certain exceptions.

But with the following example, the will throw a instead of because it uses :

Fear not, because with a new Runtime method soon-to-be-released in ZIO called , the original is thrown + all the goodies has are attached to the original stack trace!

5. Using instead of

The ZIO Environment (R parameter) allows you to inject dependencies into your ZIO based code. It is one of the key differentiators from Cats Effect. For more insights on the different ways you can inject dependencies in ZIO, I recommend this great article by Adam Warski.

Following is an example of a custom ZIO Environment dependency —

One way we can inject a concrete implementation is by creating a trait that extends with a concrete type:

Which we then provide to ZIO runtime environment via the Cake Pattern:

Once your custom Dependency is in place, the way to use it is by calling :

The problem is that expects a pure function without side-effects!

Meaning that no entry would be logged in our example ( and return effects!).
Instead you should use which expects to get an effect.
The Compiler can’t help here because a ZIO effect is just a value like any other value.

and could have just returned — but then they could potentially throw exceptions (notice how we have wrapped the original methods with and then called so they would not be able to fail.)

The same principle is also true for () () and many others.
Make sure to make conscious decisions when you choose a ZIO helper method.

(Bonus tip) Use instead of and

Most web services will emit errors logs for debugging purposes. A standard practice for logging is to catch the error, log it and re-throw it in order to fail the request. ZIO has this capability with and :

But a better method called exists that has less boilerplate and conveys intent better:

_ <- payClient.pay(cart, request.toOrder)
.tapError(ex => UIO(logger.error(s"Payment Error: $ex")))

Thank you for reading!
If you’d like to get updates on my experiences with ZIO, follow me on Twitter and Medium.

You can also visit my website, where you will find my previous blog posts, talks I gave in conferences and open-source projects I’m involved with.

If anything is unclear or you want to point out something, please comment down below.

Checkout my blog series about migrating to Bazel build tool:
Migrating to Bazel from Maven or Gradle? 5 crucial questions you should ask yourself

Wix Engineering

Architecture, scaling, mobile and web development…

Natan Silnitsky

Written by

Backend Infrastructure Developer @Wix.com

Wix Engineering

Architecture, scaling, mobile and web development, management and more, written by our very own Wix engineers. https://www.wix.engineering/

Natan Silnitsky

Written by

Backend Infrastructure Developer @Wix.com

Wix Engineering

Architecture, scaling, mobile and web development, management and more, written by our very own Wix engineers. https://www.wix.engineering/

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store