Scala at Prezi: Introduction of ZIO

Máté Börcsök
Prezi Engineering
Published in
4 min readJun 14, 2022

--

We have been using Scala to write highly performant and typesafe backend services since 2015. We built a Prezi-specific Scala Tech Stack using Akka and Future. We use Swagger to define every service’s API, which we use to generate server and client-side code. This way, Swagger is the single source of truth. Writing an endpoint begins with implementing the generated abstract function, which gives type-level guarantees about the request and response. Calling other components is also typesafe using the generated client code.
Authentication, security, and feature switches are available as Akka directives, standardized across the company.
We are delighted with this stack. It’s maintainable, testable, and robust.

Photo of a roundabout at night with traffic, symbolizing requests for our services.
Photo by Sanjeevan SatheesKumar on Unsplash

In 2021 we attended the Functional Scala conference. With the arrival of ZIO 2, we realized that there are many valuable features we currently miss in our stack. ZIO promises better performance, tracing, composability, testability, and a great developer experience. At the same time, we don’t want to throw away all the nice things described in the first chapter.

The good thing is that we don’t have to! We decided to give it a go in production, rewriting a non-critical Python service in Scala. We could easily integrate our existing Scala Tech Stack with ZIO using some thin wrappers. First, we created some ZLayers to describe our dependencies, having two implementations: one for tests and local development and one for production. These dependencies use the Future-based generated client code. We kept our existing Akka-based routing, authentication, security, and feature switcher directives as before and implemented only the business logic in ZIO. See it in action:

Code snippet of an example API implementation gluing together the existing Prezi-specific Scala Tech Stack and ZIO.

If the request was successful, we call successfulComplete. In the error case, we have a custom error type that fits our framework’s expectations, providing the correct status code, optional cause, and logging with the desired severity. We call completeWithError to respond. Service.list is pure ZIO, where we enjoy all the good things it provides.

ZIO 2 is not yet declared stable at the time of writing. But it shouldn’t stop you from using it. Using experimental features is not new for our team. We have been using Akka-typed since its early days. The only downside was some breaking changes as the library matures, but it was a small tradeoff. The documentation of the Release Candidates and the type system make it smooth to eliminate these issues.

While providing the same functionality, we experienced outstanding performance improvement, pushing p90 metrics from 200 ms to 5 ms. It’s the achievement of the language and the ability to recognize usage patterns. When you get rid of the boilerplate and noise with the help of ZIO, it becomes clear how you can improve the cumbersome parts of your business logic. Now we can utilize Memcached for basically every combination our users can request.

Grafana dashboard showing response times drop from 200 ms to 5 ms after release.
p90 response times of the old service (green) and the new service (yellow)

Based on our adventure in this small example, we would like to continue introducing ZIO in more places in the future. It was amazing to see how we can compose solutions, implement complex retry logic in one line, run things parallel, and how the explicit error types help design our system.

Initially, I thought it would be a much uglier task to do this integration. Quoting from Zionomicon by John De Goes and Adam Fraser, a book I read recently, from Chapter 5, Integrating with ZIO:

You may be happy to maintain parts of your code base using another framework, either because you are happy with the way that framework addresses your business problems or because it is just not worth migrating, and that is fine too. In that case, you can use the techniques discussed above and the tools in this chapter to continue integrating that code with the rest of your ZIO application indefinitely.

This chapter encouraged me to go with this kind of partial integration and that it will be valuable. The book was not wrong.

Read more about ZIO at zio.dev, and visit prezi.com/jobs if you want to work with us.

--

--