Futurakka, Volume I

The objective of this post is to explain the nuts and bolts of dealing with Futures in Scala and Akka. Futures allow to perform many operations in parallel in an efficient and non-blocking way, but dealing with multiple operations can be a real headache. On this post, we’ll review the main APIs and we’ll see how to apply good practices in the use of Futures.

The Reactive Manifesto establishes the principles under which the modern systems should be designed. Several of these principles, such the message-driven integration, lead to use asynchrony in order to achieve non-blocking communications.

In the modern object oriented languages (Java, Scala, Javascript) and frameworks (Akka, Play, Angular, Node.js) exist data structures to handle the asynchrony. The most relevant are the futures and the promises.The use of these structures has been gaining relevance over the last years, especially in the context of parallel and concurrent programming and distributed systems. Although they are also considered useful in several contexts, distributed or not. Some of these ones are: request-response patterns, long-running computations, database queries, RPC (Remote Procedure Call), reading data from Sockets, input/output data (reading large files from disk), managing timeouts in services, etc.

Welcome to the world of tomorrow!

The Futures are an important powerful abstraction that represents values which may or may not be currently available, but will be available at some point, or an exception if these values are available. The simplest variation includes two time-dependent states:

  • completed/determined: the computation is complete and the future’s value is available.
  • incomplete/undetermined: the computation is not yet complete.

It’s important to remark several libraries that have their own implementation of Futures, for instance, java.util.concurrent.Future, scala.actors.Future, scalaz.concurrent.Promise, com.twitter.util.Future, etc. Until Akka 2.1.x existed several classes in Akka API (included akka.dispatch.Future) which were moved to the Scala standard library.

There are some differences in the Futures between Java and Scala. Both represent a result of an asynchronous computation, but Java’s Futures are neither efficient nor composable, while the Scala Futures are. This fact makes Scala a more powerful and efficient language for handling asynchronous communications.

With the java.util.concurrent.Future implementation the only way to retrieve a value is the get method, which is blocking. Therefore there is no way to retrieving the value without blocking the execution thread. However with the scala.concurrent.Future you can manipulate the Futures without blocking; transforming them into different Futures, chaining them or attaching callbacks for completion (success/failure). In consequence, applying composition, we can describe asynchronous computations as a series of transformations.

Some of these peculiarities are reflected in Akka. For example, with Java, we have an implementation for akka.dispatch.Futures.successful while in Scala we use the native Scala method scala.concurrent.Future.successful.

Good news, everyone!

The Scala Future API includes callbacks, transformations and other auxiliary functions. The concept of a callback is easy. Let’s image you’ve done an interview for getting a job and you are waiting for a response. When the interviewer makes you a phone call this would be “the callback”.

The main callbacks in the API are andThen, onComplete, onSuccess, onFailure and forEach. Let’s see one of them:

  • andThen: applies the side-effecting function to the result of the future, and returns a new future with the result of the future.
val f = Future { 5 }
f andThen {
  case r => sys.error("runtime exception")
} andThen {
  case Failure(t) => println(t)
  case Success(v) => println(v)
}

The transformations are high order functions which help us to apply composition with futures. The most relevant are :

  • map: creates a new future by applying a function to the successful result of this future. If this future is completed with an exception then the new future will also contain this exception.
val f = Future { "The future" }
val g = f map { x: String => x + " is now!" }
  • filter: creates a new future by filtering the value of the current future with a predicate. If the current future contains a value which satisfies the predicate, the new future will also hold that value. Otherwise, the resulting future will fail with a NoSuchElementException. If the current future fails, then the resulting future also fails.
val f = Future { 5 }
val g = f filter { _ % 2 == 1 }
val h = f filter { _ % 2 == 0 }
g foreach println // Eventually prints 5
Await.result(h, Duration.Zero) // throw a NoSuchElementException
  • recover: creates a new future that will handle any matching throwable that this future might contain. If there is no match, or if this future contains a valid result then the new future will contain the same.
Future (6 / 0) recover { case e: ArithmeticException => 0 } // result: 0
Future (6 / 0) recover { case e: NotFoundException   => 0 } // result: exception
Future (6 / 2) recover { case e: ArithmeticException => 0 } // result: 3
  • fallbackTo: combines 2 Futures into a new Future, and will hold the successful value of the second Future if the first Future fails.
val f = Future { sys.error("failed") }
val g = Future { 5 }
val h = f fallbackTo g
h foreach println // Eventually prints 5
  • collect: creates a new future by mapping the value of the current future, if the given partial function is defined at that value.
val f = Future { -5 }
val g = f collect {
  case x if x < 0 => -x
}
val h = f collect {
  case x if x > 0 => x * 2
}
g foreach println // Eventually prints 5
Await.result(h, Duration.Zero) // throw a NoSuchElementException
    • As we mentioned before, we have other auxiliary functions that can be very useful dealing with Futures, for instance:
  • firstCompletedOf: value member function of the Future companion object which returns a new Future to the result of the first future in the list that is completed. This means no matter if it is completed as a success, or as a failure.
val f1 = Future { 5 } 
val f2 = Future { 10 } 
val f3 = Future { "hello" }

Future.firstCompletedOf(Seq(f1, f2, f3)) // returns 5
//in a real environment the result would be the first future resolved

I’ll let future me deal with it, he’ll know what to do

When we work with actors in Akka, we should avoid the use of Futures for achieving the most simplicity in the implementation, but this objective is not always possible. For instance, imagine an actor system which needs to integrate with external services, using REST clients implemented with Akka HTTP.  In this case, the calls to the service always return futures.

When we need to operate with Futures inside an Actor we need to convert the future objects in the model objects for using them in messages during the communication between actors. For doing this we need to import the scala package akka.pattern.pipe and use the pipeTo operator, which is an implicit conversion for using with futures.

import akka.pattern.pipe
Future { doCalc() } pipeTo nextActor
or
pipe(someFuture) to nextActor

The successful result of the future is sent as a message to the recipient, or the failure is sent in an akka.actor.Status.Failure to the recipient.

When you do things right, people won’t be sure you’ve done anything at all

As we’ve seen the futures are a powerful tool for handling asynchrony with Scala. When we design actors in Akka we should tend to not use futures but sometimes it is inevitable to use them in actors.

However, with great power comes great responsibility. Therefore is very important to know as deeply as we can the functions which the future API provides, in order to transform the future we get in the future that we need.

In the next post we’ll see another interesting API for handling asynchronous tasks : the Promise API.


References

Images

mm

Álvaro Navarro

Over the last years I've been working as a software engineer in several projects for public and private companies, mainly developing web applications and web layered based architectures to support their development. Currently I am immersing myself in the Big Data world and it's technology stack.

More Posts

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *