Scala

Reactive Database Access – Part 2 – Actors

We’re very happy to continue our a guest post series on the jOOQ blog by Manuel Bernhardt. In this blog series, Manuel will explain the motivation behind so-called reactive technologies and after introducing the concepts of Futures and Actors use them in order to access a relational database in combination with jOOQ.

manuel-bernhardtManuel Bernhardt is an independent software consultant with a passion for building web-based systems, both back-end and front-end. He is the author of “Reactive Web Applications” (Manning) and he started working with Scala, Akka and the Play Framework in 2010 after spending a long time with Java. He lives in Vienna, where he is co-organiser of the local Scala User Group. He is enthusiastic about the Scala-based technologies and the vibrant community and is looking for ways to spread its usage in the industry. He’s also scuba-diving since age 6, and can’t quite get used to the lack of sea in Austria.

This series is split in three parts, which we’ll publish over the next month:

Introduction

In our last post we introduced the concept of reactive applications, explained the merits of asynchronous programming and introduced Futures, a tool for expressing and manipulating asynchronous values.

In this post we will look into another tool for building asynchronous programs based on the concept of message-driven communication: actors.

The Actor-based concurrency model was popularized by the Erlang programming language and its most popular implementation on the JVM is the Akka concurrency toolkit.

In one way, the Actor model is object-orientation done “right”: the state of an actor can be mutable, but it is never exposed directly to the outside world. Instead, actors communicate with each other on the basis of asynchronous message-passing in which the messages themselves are immutable. An actor can only do one of three things:

  • send and receive any number of messages
  • change its behaviour or state in response to a message arriving
  • start new child actors

It is always in the hands of an actor to decide what state it is ready to share, and when to mutate it. This model therefore makes it much easier for us humans to write concurrent programs that are not riddled with race-conditions or deadlocks that we may have introduced by accidentally reading or writing outdated state or using locks as a means to avoid the latter.

In what follows we are going to see how Actors work and how to combine them with Futures.

Actor fundamentals

Actors are lightweight objects that communicate with eachother by sending and receiving messages. Each actor has amailbox in which incoming messages are queued before they get processed.

687474703a2f2f6d616e75656c2e6265726e68617264742e696f2f77702d636f6e74656e742f434830362d4163746f72732e706e67

Actors have different states: they can be started, resumed, stopped and restarted. Resuming or restarting an actor is useful when an actor crashes as we will see later on.

Actors also have an actor reference which is a means for one actor to reach another. Like a phone number, the actor reference is a pointer to an actor, and if the actor were to be restarted and replaced by a new incarnation in case of crash it would make no difference to other actors attempting to send messages to it since the only thing they know about the actor is its reference, not the identity of one particular incarnation.

Sending and receiving messages

Let’s start by creating a simple actor:

import akka.actor._

class Luke extends Actor {
  def receive = {
    case _ => // do nothing
  }
}

This is really all it takes to create an actor. But that’s not very interesting. Let’s spice things up a little and define a reaction to a given message:

import akka.actor._

case object RevelationOfFathership

class Luke extends Actor {
  def receive = {
    case RevelationOfFathership =>
      System.err.println("Noooooooooo")
  }
}

Here we go! RevelationOfFathership is a case object, i.e. an immutable message. This last detail is rather important: your messages should always be self-contained and not referencing the internal state of any actor since this would effectively leak this state to the outside, hence breaking the guarantee that only an actor can change its internal state. This last bit is paramount for actors to offer a better, more human-friendly concurrency model and for not getting any surprises.

Now that Luke knows how to appropriately respond to the inconvenient truth that Dark Vader is his father, all we need is the dark lord himself.

import akka.actor._

class Vader extends Actor {

  override def preStart(): Unit =
    context.actorSelection("akka://application/user/luke") ! RevelationOfFathership

  def receive = {
    case _ => // ...
  }
}

The Vader actor uses the preStart lifecycle method in order to trigger sending the message to his son when he gets started up. We’re using the actor’s context in order to send a message to Luke.

687474703a2f2f6d616e75656c2e6265726e68617264742e696f2f77702d636f6e74656e742f434830362d4163746f72496e74726f2e706e67

The entire sequence for running this example would look as follows:

import akka.actor._

val system = ActorSystem("application")
val luke = system.actorOf(Props[Luke], name = "luke")
val vader = system.actorOf(Props[Vader], name = "vader")

The Props are a means to describe how to obtain an instance of an actor. Since they are immutable they can be freely shared, for example accross different JVMs running on different machines (this is useful for example when operating an Akka cluster).

Actor supervision

Actors do not merely exist in the wild, but instead are part of an actor hierarchy and each actor has a parent. Actors that we create are supervised by the User Guardian of the application’s ActorSystem which is a special actor provided by Akka and responsible for supervising all actors in user space. The role of a supervising actor is to decide how to deal with the failure of a child actor and to act accordingly.

The User Guardian itself is supervised by the Root Guardian (which also supervises another special actor internal to Akka), and is itself supervised by a special actor reference. Legend says that this reference was there before all other actor references came into existence and is called “the one who walks the bubbles of space-time” (if you don’t believe me, check the official Akka documentation).

Organizing actors in hierarchies offers the advantage of encoding error handling right into the hierarchy. Each parent is responsible for the actions of their children. Should something go wrong and a child crash, the parent would have the opportunity to restart it.

Vader, for example, has a few storm troopers:

import akka.actor._
import akka.routing._

class Vader extends Actor {

  val troopers: ActorRef = context.actorOf(
    RoundRobinPool(8).props(Props[StromTrooper])
  )
}

The RoundRobinPool is a means of expressing the fact that messages sent to troopers will be sent to each trooper child one after the other. Routers encode strategies for sending messages to several actors at once, Akka provides many predefined routers.

687474703a2f2f6d616e75656c2e6265726e68617264742e696f2f77702d636f6e74656e742f4b696c6c656453746f726d74726f6f706572732e6a7067

Ultimately, actors can crash, and it is then the job of the supervisor to decide what to do. The decision-making mechanism is represented by a so-called supervision strategy. For example, Vader could decide to retry restarting a storm trooper 3 times before giving up and stopping it:

import akka.actor._

class Vader extends Actor {

  val troopers: ActorRef = context.actorOf(
    RoundRobinPool(8).props(Props[StromTrooper])
  )

  override def supervisorStrategy =
    OneForOneStrategy(maxNrOfRetries = 3) {
      case t: Throwable =>
        log.error("StormTrooper down!", t)
        SupervisorStrategy.Restart
    }
}

This supervision strategy is rather crude since it deals with all types of Throwable in the same fashion. We will see in our next post that supervision strategies are an effective means of reacting to different types of failures in different ways.

Combining Futures and Actors

There is one golden rule of working with actors: you should not perform any blocking operation such as for example a blocking network call. The reason is simple: if the actor blocks, it can not process incoming messages which may lead to a full mailbox (or rather, since the default mailbox used by actors is unbounded, to an OutOfMemoryException.

This is why it may be useful to be able to use Futures within actors. The pipe pattern is designed to do just that: it send the result of a Future to an actor:

import akka.actor._
import akka.pattern.pipe

class Luke extends Actor {
  def receive = {
    case RevelationOfFathership =>
      sendTweet("Nooooooo") pipeTo self
    case tsr: TweetSendingResult =>
      // ...
  }

  def sendTweet(msg: String): Future[TweetSendingResult] = ...
}

In this example we call the sendTweet Future upon reception of the RevelationOfFathership and use the pipeTo method to indicate that we would like the result of the Future to be sent to ourselves.

There is just one problem with the code above: if the Future were to fail, we would receive the failed throwable in a rather inconvenient format, wrapped in a message of type akka.actor.Status.Failure, without any useful context. This is why it may be more appropriate to recover failures before piping the result:

import akka.actor._
import akka.pattern.pipe
import scala.control.NonFatal

class Luke extends Actor {
  def receive = {
    case RevelationOfFathership =>
      val message = "Nooooooo"
      sendTweet(message) recover {
        case NonFatal(t) => TweetSendFailure(message, t)
      } pipeTo self
    case tsr: TweetSendingResult =>
      // ...
    case tsf: TweetSendingFailure =>
      // ...
  }

  def sendTweet(msg: String): Future[TweetSendingResult] = ...
}

With this failure handling we now know which message failed to be sent on Twitter and can take an appropriate action (e.g. re-try sending it).

That’s it for this short introduction to Actors. In the next and last post of this series we will see how to use Futures and Actors in combination for reactive database access.

Read on

Stay tuned as we’ll publish Part 3 shortly as a part of this series:

Lukas Eder

Lukas is a Java and SQL enthusiast developer. He created the Data Geekery GmbH. He is the creator of jOOQ, a comprehensive SQL library for Java, and he is blogging mostly about these three topics: Java, SQL and jOOQ.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button