Writing Async App in Scala

Part 1. Coding

Dmitry Komanov
Wix Engineering
5 min readMay 23, 2020

--

In this part we will cover the most basic part of async programming in Scala with Futures.
Part 1. Coding.
Part 2. Exception Handling.
Part 3. Threading Model.

Today asynchronous programming gets some traction. Some may argue even some hype. However, there are cases when it’s really necessary (or, let’s put it mildly, favorably). In this blog post I’d like to put aside reasons behind writing an application in an async fashion, it’s a separate topic. Here I want to focus on the practical side of this topic — how it looks like, what we have to do in order to make it simpler, and what problems we may encounter. Everything here is based on my personal experience, so I hope it won’t be too academic.

Another thing, I’m not going to explore different approaches of achieving asynchronicity (actor model, functional approaches, etc.) I’ve chosen approach with Future model of Scala because of the least additional cost of learning. This concept is easier understandable both for me and for people around me: leap from sync jala (java style on Scala) programming to Futures is just shorter.

This post is written under the assumption that the reader is familiar with the concept of Future/Promise model and familiar with its implementation in Scala. If not, I would advise to read these articles first: Asynchronous Programming and Scala by Alexandru Nedelcu and The Neophyte’s Guide to Scala Part 8: Welcome to the Future
by Daniel Westheide (entire Neophyte’s Guide is awesome!).

After such a long disclaimer, I have one more thing to say. A kind of application that I’m having in mind is simple: a backend for DB (not really important which one) with some REST/RPC exposure to the world (or internal world), which may also communicate via the network with other applications. Regular “web” application, without any CPU heavy operations (like image processing or blockchain computations.)

Regular web application

In our synchronous world, the application may look like this:

An example of a regular web application (sync style)

By the end of this series I hope it will be clear, how to rewrite application in asynchronous way and avoid some dangers of async approach.

Writing in async fashion

NB: For simplicity let’s not think about implicit ec: ExecutionContext argument for Future operations yet. I will cover this topic in next parts.

A straight-forward rewrite would look like:

Naive attempt to rewrite sync code in an async fashion.

In the following examples, I’ll show how to write a bit prettier…

if-statement replacement

Suppose we have old-school future-less code:

Synchronous if-statement

How it would look like with futures? Suppose, all methods use some kind of IO inside, so:

Asynchronous version of if-statement

Pretty straight-forward, right?

Boolean operations

What if there are multiple conditions?

There aren’t much help from scala-library itself here, so let’s extend it a bit:

Extensions for Future[Boolean]: && and || functions.

And now, with these extension methods we will get this:

Nice, right?

We can go further, for example, if some of conditions aren’t IO-bound, or already calculated, we can just extend it a little bit more:

Extensions for Boolean type to mix Future[Boolean] and Boolean.

And use it:

Boolean operations for mixed Boolean and Future[Boolean] operand types.

Same code, but now we can mix Future[Boolean] and Boolean. Well, almost… We also need to add corresponding extension methods to Future[Boolean] for commutativity, which is a bit more complex, because we can’t overload method with arguments => Boolean and => Future[Boolean], so we need to use some implicit magic (called type classes):

Type Classes for solving commutativity problem for Future[Boolean] and Boolean mix.

I hope you’ve got the idea: whatever regular boolean operation you need, just extend Future[Boolean] and Boolean classes to support it, and your code will look beautiful and simple.

for-comprehensions for the rescue

Another way of making code look like future-less code is usage of for-comprehensions. This topic is covered quite a lot (1, 2, 3). In short, it looks like this:

Dealing with filter

Looks very familiar. But in real life, it’s more complicated:

Close to real life for-comprehensions examp

if construct is supported (via withFilter method of Future), of course, but Future will be resolved with NoSuchElementException, without ability to understand what’s really happened there.

I came up with this solution (yes, another extension method):

Extending Future[Boolean] with orFail method.

And now we can rewrite it in this form:

Now there will be PermissionDeniedException if user doesn’t have permissions, but still NoSuchElementException if movie is hidden. One of solutions might be this: instead of just comprehending getMovie, we may make it slightly more complex:

Localize filter and recoverWith.

As always, we can simplify it with simple extension method:

Extending Future[T] with filterOrFail method.

Much better now:

Usage of filterOrFail extension method improves readability for filter..recoverWith.

Another filter-related consideration

What I really don’t like about regular filter is this NoSuchElementException. When we use recover or recoverWith right after, it means, that this exception is created only to be catched very match soon and will be replaced with something else. In a hell for p̶e̶r̶f̶e̶c̶t̶i̶o̶n̶i̶s̶t̶s people who waste their time on micro-optimizations there is a never-ending loop:

try {
throw new NoSuchElementException
} catch {
case _: NoSuchElementException =>
}

To deal with my control freak’s issue I’ve created simple singleton exception without a stack-trace:

object ControlException extends Throwable("", null, false, false) {
def unapply(e: Throwable): Boolean =
e.isInstanceOf[ControlException.type]
}

And whenever I extend Future in a way that I need to call recover method after, I use this ControlException which literally has close to zero overhead. Of course, I have convenient extension methods supporting it:

recoverFilter and recoverFilterWith extension methods to handle NoSuchElementException and ControlException conveniently.

Dealing with Option

Another problem with for-comprehensions is that you can’t mix different monads (like Option, in the world of functional programming it is solved task, this piece is for those who isn’t there ;-) ). Because in real life it’s quite common to expose methods like:

def findMovie(title: String): Future[Option[Movie]]

In this case, in for-comprehensions in this expression movie <- findMovie(“Dark Waters”) the type of movie will be Option[Movie], not Movie. Which is understandable, but we need to find out how to deal with it. One way is to expose Option-less version which resolves Future with some Exception. Another approach is, as you may already have guessed, to create a convenient extension method:

Extension method to fold Future[Option].

And usage:

Usage of orFail method in for-comprehensions.

Conclusion

This part is the most basic of async programming: coding in async fashion. But still, when I started to write more and more code I realized how vanilla Scala is not sufficient to do most of the work — I import my extensions in every second file where I write something with Futures.

And because Future functionality is very limited out of the box, don’t hesitate to extend it to make the life easier and the code more readable.

All code is available on GitHub.

--

--

Dmitry Komanov
Wix Engineering

Software developer, moved to Israel from Russia, trying to be aware of things.