Using Akka for writing distributed Scala applications
What will I learn?
This tutorial will teach you following topics:
- how to think in terms of actor model
- how to build event-driven applications
- how not to get lost in the world of distributed programming
Requirements
- Experience programming in scala
- Experience working in command line
Difficulty
- Intermediate
Tutorial Contents
In this tutorial we will write simple application in scala that uses akka toolkit. To follow this tutorial, make sure you have the following components installed in your system:
- Java SDK (install instructions)
- Scala Build Tool (sbt) (install instructions)
Why akka
When working in distributed environment, programmer meets many new challanges that do no occur in development of apps that run locally. To name a few:
- failure tolerance: any of the system's component can crash at any moment. The system as a whole is supposed to be able to recover from such failure.
- load balancing: to use available resources most efficiently, tasks are supposed to be distributed between system components in a way that minimises idle time.
- unpredictable network conditions: communication between system componens might suffer delays, or messages may not be delivered altogether.
Akka adresses this problems by presenting high-level API for component communication, and abstracting the low level details of the communication channels. By doing so, Akka allows developer to concentrate on writing distributed and scalable code without spending too much time solving problems specific to distributed programming.
Akka philosophy
In traditional OOP world we use method calls as a most primitive trigger to execute some action. Method call is a blocking operation that has access to object state, takes some inputs and returns an output.
Akka uses actors and message passing as kind of replacement for method calls. Actor can receive a message and execute some code to process it. Even though actors are fulfilling the same task as methods (executing the task), they possess a few key differences:
- Sending message to Actor is a non-blocking operation. Thread of execution continues immediately, without waiting for an actor to finish its tasks
- Actors do not have a return value. Instead, they might send a reply message, as well as send another message to another actor whenever needed.
Every message that comes to an actor first goes to inbox
- incoming message queue. Actor will handle them in order. Whenever actor finishes processing a message and risks becoming idle, it will look up the oldest message in queue and start processing it. If inbox
is empty, it idly waits for new messages to come.
Actors have behaviours
- internal state consisting of local variables. Any incoming message can change behaviour of the actor. Yet, since messages are always processed in order, and state is encapsulated inside one actor, modifying its internal state is safe. No locks are needed anywhere.
Setting up scala project
Create a directory akka-simple-example
for this tutorial. Inside, we need to create directories representing classical scala project structure:
mkdir -p src/main/scala/io/utopian/laxam/akka/simple # main location for our code
mkdir -p src/test/scala/io/utopian/laxam/akka/simple # location for tests
mkdir project # for sbt settings
Now create project configuration file build.sbt
in the root folder of your project. This file will contain paramters like project's name and version, version of scala to use and versioned dependency information.
name := "akka-simple-example"
version := "1.0"
scalaVersion := "2.12.5"
lazy val akkaVersion = "2.5.11"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-testkit" % akkaVersion,
"org.scalatest" %% "scalatest" % "3.0.5" % "test"
)
Once done, run sbt
in command line and it will update a few files and finalize project setup.
Writing Akka application
Our application will contain 2 types of actors:
parser
will perform a simple task of converting string with hex or octal number to integerprinter
will convert integer to decimal string representation and output it to console
First, let's declare package and import required Akka objects and classes:
package io.utopian.laxam.akka.simple
import akka.actor.{ Actor, ActorLogging, ActorRef, ActorSystem, Props }
Actor is created with actorOf
function, which takes Props
instance and optionally the name of an actor as a string. here is the simplest example of creating Props
and starting corresponding actor:
val props = Props(new ActorClass(arg))
system.actorOf(props, "exampleActor")
While this approach works, it is considered a better style to build props in a factory method of companion object of actor's class. So let's do that for our Printer
class:
object Printer {
def props: Props = Props[Printer] // Printer does not expect any arguments, so we instantiate Props as a generic without arguments
}
Companion object is also a good place to define case classes and case objects to be used for message passing in the actor. For Printer
we need one case class with two arguments:
from
: original string that was convertedresult
: integer representation offrom
This means we need to extendPrinter
object with following case class:
final case class Output(from: String, result: Int)
With companion object ready, we can write actual actor:
class Printer extends Actor with ActorLogging {
import Printer._
def receive = {
case Output(from, result) =>
log.info(s"${sender()}: $from => ${result.toString}")
}
}
Now let's write Parser
. We plan to have two kinds of messages: Parse
to trigger parsing and Print
to send parsed data to Printer
for output. Actor needs to be generic enough to work with different bases (octal or hexadecimal). It also needs reference to the printer actor. So companion banner will look like this:
object IntParser {
final case class Parse(s: String)
case object Print
def hexProps(printer: ActorRef): Props = Props(new IntParser(16, printer))
def octProps(printer: ActorRef): Props = Props(new IntParser(8, printer))
}
As you can see, companion object has two props methods in this case: one for building an octal conversion actor and one for hex conversion.
Now the actor itself:
class IntParser(base: Int, printer: ActorRef) extends Actor {
import IntParser._
import Printer._
var result = 0;
var from = "";
def receive = {
case Parse(s) =>
from = s
result = Integer.parseInt(s, base)
case Print =>
printer ! Output(from, result)
}
}
Simply enough, it applies conversion and stores result into variable on Parse
message, and sends message to printer
on Print
message. Now lets initiate akka and send couple of messages to see how it works:
object AkkaSimpleExample extends App {
import IntParser._
val system: ActorSystem = ActorSystem("simpleAkka")
val printer: ActorRef = system.actorOf(Printer.props, "printerActor")
val hexParser: ActorRef = system.actorOf(IntParser.hexProps(printer), "hexParser")
val octParser: ActorRef = system.actorOf(IntParser.octProps(printer), "octParser")
hexParser ! Parse("FFFF")
hexParser ! Print
octParser ! Parse("1001")
octParser ! Print
hexParser ! Parse("F")
hexParser ! Print
octParser ! Parse("404")
octParser ! Print
hexParser ! Parse("404")
hexParser ! Print
}
Our akka application is done, it's time to test it. Enter sbt
promt and run runMain io.utopian.laxam.akka.simple.AkkaSimpleExample
to see the output.
As you can see from the screenshot, order of execution is only guaranteed in bounds of one actor, e.g. message hexParser ! Parse("FFFF")
will always finish before hexParser ! Parse("F")
has started, but octParser ! Parse("1001")
can be executed before or after them (or in between) because octParser is a separate actor with it's own inbox
queue.
Complete project is available in my GitHub repository: akka-simple-example.
I hope you have enjoyed this tutorial.
Posted on Utopian.io - Rewarding Open Source Contributors
Hey @laxam I am @utopian-io. I have just upvoted you!
Achievements
Utopian Witness!
Participate on Discord. Lets GROW TOGETHER!
Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x