Reader small image

You're reading from  Learning Scala Programming

Product typeBook
Published inJan 2018
Reading LevelBeginner
PublisherPackt
ISBN-139781788392822
Edition1st Edition
Languages
Tools
Right arrow
Author (1)
Vikash Sharma
Vikash Sharma
author image
Vikash Sharma

Vikash Sharma is a software developer and open source technology evangelist. He tries to keep things simple, which helps him write clean and manageable code. He has invested a large amount of time learning and implementing Scala code, and he has authored video courses for Scala. He works as a developer at SAP Labs.
Read more about Vikash Sharma

Right arrow

Chapter 12. Introduction to Akka

"Technology is nothing. What's important is that you have faith in people, that they're good and smart, and if you give them tools, they'll do wonderful things with them."

- Steve Jobs

As developers, we are used to facing programming problems and solving them using abstractions, programming models, or some design patterns. These programming models tend to make ours and consumer's lives easier. This chapter is about learning one such programming model that solves more than one problem. We'll understand and work with Akka, which is based on Actor models. We can think of Akka libraries (well mostly) as an open source set of libraries that help you write concurrent, fault tolerant, and distributed applications. We'll talk about what you might expect from this toolkit. As we go through the chapter, we'll try understanding the actor model and how these actors work together, as well as how the actor mechanism is different from any other concurrency mechanism.

Going...

Why do we care about Akka?


With a large amount of data all around us, our computer/processing systems are striving for performance. With multicore architecture and distributed computing, we are achieving high performance with acceptable availability of services. But this cannot be taken for granted; we have come to a point where we already have mechanisms to deal with problems that arise due to either incapability of systems or the programming models we are using.

Due to the advent of multicore architecture, our systems are capable of processing a large amount of data with high performance. But there is a fault in our programming models, which we use to mutate states, and at the same time use several threads to vary the states present in our programs. This has given us a reason to think.

Two or more threads trying to process a particular shared state might cause a deadlock (more on this in Chapter 13, Concurrent Programming in Scala, where we discuss concurrency and threads in more detail...

What's up with the Actor Model?


From our discussion, it's clear that we have some entities that act on receipt of some messages, or let's say requests. We call them Actors. To solve some domain-specific problems, we might want to have more than one Actor. Think of a basic scenario of e-commerce checkout flow. There's more than one area of concern. The following diagram represents the basic intended flow:

By taking a look at the diagram, it's clear that we have few entities, and these entities are going to take care of specific concerns. We have named these entities based on their area of concern:

  • CheckoutActor might have the responsibility to fetch details from the cart and show the respective information.
  • Additionally, you might want to apply some coupon code or offer. Our system has to validate that coupon or offer code and based on that we might want to modify order details. For this particular process, we have HandleOffersActor.
  • ShippingActor has the responsibility of fetching user-specific...

Understanding the Actor system


Akka documentation simply explains an ActorSystem as a heavyweight structure that will allocate 1 to N threads, and we should create one per logical application. Once we create an actor system, we get the license to create actors under that system. We'll take a look at how we can create Actors in the next sections.

When we create actors as part of a system, these actors share the same configuration (such as dispatchers, paths, and addresses) as the Actor system.

Within an Actor system, there's a root guardian Actor; this serves as a parent actor to all actors residing within an actor system, internal actors, as well actors that we create. So, as expected, this is the last actor to be stopped when the system terminates.

The reason why Akka provides these guardian actors is to supervise the first-level actors we create, so for user created actors too, we have a specific user guardian. Similarly, for system provided actors, Akka has system guardian.

Take a look at...

Props


Props can be seen as a configuration object for ActorRef. We can create instances of the props configuration with some configuration. The following is an example of that:

val props = Props[SimpleActor]() 
  .withDispatcher("some-simple-dispatcher") 
 
val simpleActor: ActorRef = system.actorOf(props, "simple-actor") 

One important thing to know about the Props object is that it is immutable and hence thread-safe.

Actor references and paths


When we create an Actor, what we get in response is an ActorRef. This is a reference to our created Actor. Why we might need an ActorRef is to pass throughout the system to other actors as a reference. These references are used for message passing. Every actor that we create has a reference to itself through self.

From within an actor, it's possible to obtain an actor reference of the calling Actor via a method named sender().

We can also give names to actor references. In our case, we named our SimpleActor reference simple-actor:

val simpleActor: ActorRef = system.actorOf(props, "simple-actor")

We also know that these Actors are created in a hierarchical fashion and we can give unique names to actor instances. Hence, these names together make a path for each Actor. The path is unique for each Actor. Our SimpleActor path might look like this:

We can see that due to the hierarchy, we have paths for different actors, because actors must have unique names. We can also...

Selecting existing actorRefs via actorSelection


Due to every actor having its own unique ID, we can refer to a particular actor via its path using the actorSelection method. We can call the actorSelection method on system or context and get the ActorRef.

When we call actorSelection on system, we need to pass the absolute Actor path starting from root, whereas while calling the same on context, we can pass the path relative to the current Actor.

Assuming the current Actor (first-level Actor) has a SiblingActor, at the same level, we may refer to the sibling Actor's actor reference as:

context.actorSelection("../siblingActor") 
 
context.actorSelection("/user/siblingActor") 

In these two approaches, the first one used to represent the parent Actor. The other approach directly referred to the Actor's path. With this, we were able to get the actor references, but it's discouraged because we might not want to write actor paths explicitly. We can leverage use of actorSelectionwhen suppose we want...

How the Actor life cycle works


When we make a call to method actorOf, what we get in return is an ActorRef that in turn also possesses a particular path where we've created the Actor. With this call, we know exactly there's an Actor instance created, been assigned a unique ID, and hook methods are called. There's this method named preStart() that gets called as the very first action, after a new Actor is created.

A few points to note when a new Actor is created:

  • A new Actor path is reserved for the Actor
  • A unique ID is assigned to the Actor
  • After the instance is created, the preStart() method is called:

When an Actor is restarted:

  1. The preRestart() is called on the instance.
  2. New instance is created, replaces the old instance.
  3. The postRestart() method is called.

When an Actor is stopped:

  1. The postStop() method is called on the instance.
  2. Terminated message is sent to watchers.
  3. Actor path is allowed to be used again.

The previous diagram illustrates this whole cycle. An important point to note is that we...

Hello world in Akka


For writing our first Akka actor, we need to add the akka-actor library dependency. For dependency management we'll be using SBT and, as we know, we'll be defining these library dependencies in our build.sbt file. To do this, we need to have SBT installed on our system.

Setting up the environment

To get started with a simple Akka project, we can simply follow these steps:

  1. Go to Lightbend's TECH HUB (https://developer.lightbend.com) and click on START A PROJECT:
  1. Search for Akka Quickstart Scala under Akka projects:
  1. Click on CREATE A PROJECT FOR ME!:
  1. Extract the downloaded ZIP (compressed) file.

We can open the extracted folder in IntelliJ IDEA IDE:

  1. Open IntelliJ IDE.
  2. Click on File | New | Project from Existing Sources...:
  1. Choose the build.sbt from the project (akka-quickstart-scala) we've just extracted:
  1. And you get the project open in the IntelliJ window:

This is one way of starting with an Akka project. This project has already defined all the akka-actors specific dependencies...

Writing our first Actor


Writing an Actor is as simple as writing a class that extends the akka.actor.Actor class. And we know that Actors respond to messages, so to identify messages, we have a method named receive that we have to define for each Actor we write. Let's write our SimpleActor:

 

import akka.actor.Actor 
 
class SimpleActor extends Actor { 
    
  override def receive = Actor.emptyBehavior 
 
} 

So, we wrote the SimpleActor with some empty behavior defined in the receive method. But here we've just wrote our Actor; we have to instantiate the Actor as part of an Actor system. After instantiating, we might also want to run our application to see the behavior, hence, let's write the entry point to our application and instantiate an Actor system:

 

import akka.actor.ActorSystem 
 
object AkkaStarter extends App { 
 
  val simpleActorSystem = ActorSystem("SimpleActorSystem") 
 
} 

This statement gives us an instance for an Actor system with the name SimpleActorSystem. Now, we want to create...

The tell versus ask versus forward method


We use one of these three approaches to transmit messages from one actor to another. As we've already established, tell transmits messages and does not wait for the response; this way, we ensure at most once delivery. We can also use the ask method in cases where we expect our called actors to respond back with some messages of the response type. There might be scenarios where you want to forward a message of a particular type with the same actor reference (ActorRef) to another Actor. For this purpose, we can use the forward method:

class AnotherActor extends Actor { 
  override def receive = { 
    case ShowFootballPlayersRequest(url) => { 
      val playersInfoSource = Source.fromFile(url) 
 
      val players = asPlayers(bufferedSourceToList(playersInfoSource)) 
      players.foreach(player => println(player + "n")) 
    } 
  } 
} 
 
object AnotherActor { 
  val props = Props[AnotherActor] 
} 

We have defined AnotherActor, and we can make...

Stopping Actors


One way of stopping actors is by calling the stop method from the system or context for a particular actor. To do this, we can define a particular message that can be passed to the actor, telling it to stop. For example:

case "terminate" => context stop self 

Most of the times the preferred way of terminating an actor is by sending a PoisonPill message to it:

simpleActor ! PoisonPill 

This simple message passing can terminate the actor gracefully. The termination takes place after all the messages in the Actor's queue are processed before the poison pill is processed. Stopping an actor stops all of its child actors. Remember, we talked about those hook methods that can be called if we want to perform some logic when the actor is starting up or at termination. Let's take a look at those.

The preStart and postStop hooks


Let's define these methods in the SimpleActor class to log the starting and stopping of our SimpleActor:

override def preStart(): Unit = log.info("SimpleActor starting!") 
 
override def postStop(): Unit = log.info("SimpleActor stopping!") 

Run:

[INFO] [12/27/2017 14:56:54.887] [SimpleActorSystem-akka.actor.default-dispatcher-3] [akka://SimpleActorSystem/user/simple-actor] SimpleActor starting! 
[INFO] [12/27/2017 14:56:54.915] [SimpleActorSystem-akka.actor.default-dispatcher-2] [akka://SimpleActorSystem/user/simple-actor] Executing GetPlayerInformationRequest(Cristiano Ronaldo, listOfPlayers) 
Player(Cristiano Ronaldo,Portugal,32,Real Madrid,Spain,4829,4789,1,2) 
[INFO] [12/27/2017 14:56:54.938] [SimpleActorSystem-akka.actor.default-dispatcher-2] [akka://SimpleActorSystem/user/simple-actor] SimpleActor stopping! 

Similar methods are also available for restart operations in the form of preRestart and postRestart.

When we discuss communicating via messages, the...

Actor communication via messages and its semantics


We talked about the fashion of fire-and-forget regarding message passing; to understand this a bit more, let's take a look at a diagram explaining message delivery semantics.

The following diagram explains the semantics of message delivery; when we send messages over a network, there are chances of it getting delivered and also chances of it being lost. Also, in the case of an unsuccessful or successful attempt to deliver a message, we might try to send a message or we might not. It depends on us if want to try sending a message exactly once and then not the second time or so on.

Based on these assumptions, we can make some formal terminologies specifying what we discussed, and we call them:

  • At most once
  • At least once
  • Exactly once

The diagram explains each of the three approaches in simple terms. It's important to know that in case of the Actor's communication, we have at most once delivery; in other terms, it means no guaranteed delivery. When...

Supervising fault in our actors


There is a possibility that our logic ends up in a network error or some unexpected exception. Imagine a scenario where our service needs to call a particular database instance to fetch some data. We might face connection timed out or some other similar errors. In that case, what should be our behavior? Maybe trying to establish the connection a couple of times will help, this can be achieved if our tasks are performed in such a hierarchical manner. We can achieve this task by performing hierarchy via the actors in place. And if some actor from down in the hierarchy fails and can communicate the failure to parent actor, the parent actor, based on the type of failure, can restart/kill the actor or perform some other operation as required. This is in a sense supervising the actors below in the hierarchy; let's say parent actors can supervise child actors. The way we define this strategy comes under the Akka defined supervision strategy.

Supervision in a sense...

OneForOne versus AllForOne strategy


Take the scenario where we have SimpleActor and AnotherSimpleActor actors. There's one child actor for SimpleActor named SimplerrrActor:

  • SimpleActor: /user/topLevelActor/simpleActor
  • AnotherSimpleActor: /user/topLevelActor/anotherSimpleActor
  • SimplerrrActor: /user/topLevelActor/simpleActor/simplerrrActor

In such cases, the user guardian is going to take care of topLevelActor and topLevelActor is going to supervise SimpleActor and AnotherSimpleActor. If something goes wrong in SimpleActor and we want all the actors to resume/restart/stop, we can define an AllForOneStrategy. If we want to perform such actions only on the failed SimpleActor and its subsequent children, we can opt for OneForOneStrategy.

These two are defined as case classes in Scala, which takes a few parameters in the form of maxNrOfRetries, withinTimeRange, and loggingEnabled:

case class OneForOneStrategy( 
  maxNrOfRetries:              Int      = -1, 
  withinTimeRange:             Duration =...

Default supervision strategy


By default, the Akka system looks for a few exception types from failure messages received from child Actors. Let's take a look at those scenarios.

The default supervision strategy will stop the failing child Actor in case of:

  • ActorInitializationException
  • ActorKilledException
  • DeathPactException

Note that in case of exception, it'll restart the failing Actor.

With this information, let's try implementing one strategy on our own.

Applying the supervision strategy


While overriding the default supervisorStrategy, all we do is define the value with arguments and provide a Decider; this decider contains the logic to be implemented in case of exceptions. It looks like this:

import akka.actor.SupervisorStrategy.{Resume, Restart} 
 
override val supervisorStrategy = 
  OneForOneStrategy( 
    maxNrOfRetries = 3, 
    withinTimeRange = 1 minute 
  ){ 
    case _: ArithmeticException => { 
      log.info("Supervisor handling ArithmeticException! n Resuming!") 
      Resume 
    } 
    case _: Exception => { 
      log.info("Supervisor handling Exception! n Restarting!") 
      Restart 
    } 
  } 

Here, we have defined a OneForOneStrategy, and on a case by case basis, the action to be performed in regards to the failing actor. A full example with this strategy in place can look like the following:

package example 
 
import akka.actor.{Actor, ActorSystem, OneForOneStrategy, Props, ActorLogging} 
import scala.concurrent...

Testing actors


For the testing of Actors that we create, we might consider few entities that are needed to be present. These entities might be:

  • A test actor-system
  • A testActor (the message sender)
  • The actor under testing (whose behavior we want to test)
  • Assertions to be made in case of an actors expected message

Akka's test-kit library provides us with all of these needed entities ready-made. We can use these to test our actors. Let's write a simple actor test case.

The expect case is to check if our GetPlayerInformationRequest works fine:

package lsp 
 
import akka.actor.ActorSystem 
import akka.testkit.{ImplicitSender, TestKit} 
import lsp.SimpleActor.{GetPlayerInformationRequest, PlayerInformationResponse} 
import org.scalatest.{BeforeAndAfterAll, WordSpecLike} 
 
class SimpleActorSpec extends TestKit(ActorSystem("testActorSystem")) 
  with ImplicitSender with WordSpecLike with BeforeAndAfterAll { 
 
  override def afterAll(): Unit = super.afterAll() 
 
  val players = List(Player("Cristiano...

Summary


This chapter was an introduction to Akka for us. We tried to understand the basic underlying principle of actors. We've covered one of the most important libraries Akka provides, akka-actors. Starting from why we need a library of this sort, to understanding the way we implement Actors in Akka, we covered it all. Then from there, we covered the important supervision strategy in Akka. We talked about and practiced our own custom supervisor strategy. Finally, we had a look at akka-testkit, a testkit provided by Akka. With this, we covered what was needed to understand Akka actors and the basics of it. In the next chapter, we'll focus on how we can handle concurrency in Scala. We know it's importance in modern architecture, so the next chapter will be an exciting one.

 

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Learning Scala Programming
Published in: Jan 2018Publisher: PacktISBN-13: 9781788392822
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Vikash Sharma

Vikash Sharma is a software developer and open source technology evangelist. He tries to keep things simple, which helps him write clean and manageable code. He has invested a large amount of time learning and implementing Scala code, and he has authored video courses for Scala. He works as a developer at SAP Labs.
Read more about Vikash Sharma