Using Akka for writing distributed Scala applications

in #utopian-io4 years ago (edited)

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:

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 integer
  • printer 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 converted
  • result: integer representation of from
    This means we need to extend Printer 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.

Screen Shot 2018-04-10 at 07.30.13.png

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

Sort:  

Hey @laxam I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

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

Coin Marketplace

STEEM 0.42
TRX 0.07
JST 0.052
BTC 43076.81
ETH 3335.60
BNB 500.53
SBD 4.97