So last time, I outlined the road map for this series of articles on Akka, and posted up some information from the Akka creators on what they had to say about Akka.
This time, we will look at a simple example of Akka.
But just before we do that, let’s talk a bit more about WHY I think Akka is great stuff.
I have been a .NET programmer for many years and have seen asynchronous programming come in many different flavors now:
- Asynchronous delegates
- BackgroundWorker
- Task (TPL)
- Async Await
- RX
- Concurrent collections
- Critical sections (synchronized sections of code)
All of these pretty much have their JVM equivalent, and what you would have likely seen if you have used the .NET or JVM equivalents is the use of locks from time to time. For example, under the covers, the concurrent collections would still use some locking (monitor enter/exit) to achieve the critical sections.
This is all good stuff, and has got better to work with over the years, but there could be a better more elegant lock free way of working with concurrent/parallel programming.
For me, this is what Akka brings to the table. Instead of working with shared state that MUST be protected when writing multithreaded code, we simply don’t use any shared state and create dedicated micro sized bits of code that deal with one thing and one thing only. These are called “Actors”.
Actors do NOT share state, instead they work independently of each other and rely on message passing. Where the message payload should give the actor either everything it needs to do its job (or at the very least enough information to perhaps look things up, say an Id
such that Actor
can look up the entity required by its Id
field).
At no point will we be using locks within actors.
Ok so that is my mini-rant/intro over. Let's now carry on and have a look at what it takes to write some Akka code in Scala.
What Libs Do We Need?
As I mentioned in the introduction post, I will be using Scala exclusively to do this series of posts. As such, I shall also be using SBT to do the build side of things.
So for this post, we are only showing how to use simple actors so we don’t have to pull in that many dependencies, we can keep things simple and use the following “build.sbt” file, where I am pulling in the following 2 dependencies:
- Basic Akka stuff
- Joda time
name := "HelloWorld"
version := "1.0"
scalaVersion := "2.11.8"
libraryDependencies ++= Seq(
"com.typesafe.akka" % "akka-actor_2.11" % "2.4.8",
"joda-time" % "joda-time" % "2.9.4")
NOTE: This SBT set of dependencies may grow in subsequent articles, but where it does require pulling in more Akka JARs I will show that when the time comes.
How Do We Create An Actor System?
To use the Akka actor system, we must first create an Akka system that is the fabric that all actors run under. This is easily achieved as follows:
object Demo extends App {
//create the actor system
val system = ActorSystem("HelloSystem")
//---------------------------
// EXTRA STUFF WILL GO HERE
//---------------------------
//shutdown the actor system
system.terminate()
StdIn.readLine()
}
Note that you should ensure that the Akka system is also shutdown correctly.
The example I show here is a simple console type application, so my startup/shutdown logic is all in the same file, but in a real production app, things may be more complex (well, they should be I would hope).
How Do We Create An Actor?
Now that we have an actor system, the next thing we need to do is create some actors that will live within the actor system. I used the word live as a ownership type of arrangement.
So how do we create an actor
exactly?
Well luckily this too is dead simple. We just need to inherit from Actor
and provide an implementation for the receive
method to handle the different messages that may be sent to the actor
.
Recall that an actor
works by receiving messages and acting upon them.
Here is what the skeleton code may look like:
import akka.actor.Actor
class HelloActor extends Actor {
def receive = {
//DO THE MESSAGE HANDLING HERE
}
}
We will talk more about the receive
method in a while, for now, just know that you must implement this method for an Akka actor
to work correctly
Difference Between Tell And Ask
Just before we get on to seeing the examples, let's just take a brief diversion where we talk about the difference between ask and tell.
When we ask (? method in scala) an actor
, we expect a response by way of a Future[T]
, this is an asynchronous operation.
When we tell (! method in scala) an actor
something, this is analogous to fire and forget (of course the receiving actor
could send a response back to the initiating actor
via a different message, but that’s a different story), this is an asynchronous operation that returns immediately.
Sender
We have not covered this ground yet, but we will in one of the subsequent posts, but for now all you need to know is that when you are sending messages to an actor
, you do so by a construct called actorRef
which is a kind of like a handle to an actor
.
There are special types of actorRef
, one such case being “sender” who is the initiator actorRef
of the message being received (if we are talking from the context of the receiving actor
). You will see “sender
” used in the examples below.
How Do We Send A Message To An Actor?
Here is how we would send a message to an actor. This is a tell (!) so is fire and forget.
import akka.actor._
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.concurrent.duration._
import scala.language.postfixOps
import scala.io.StdIn
import scala.util.{Success, Failure}
import ExecutionContext.Implicits.global
object Demo extends App {
//create the actor system
val system = ActorSystem("HelloSystem")
// default Actor constructor
val helloActor = system.actorOf(Props[HelloActor], name = "helloactor")
//send some messages to the HelloActor (fire and forget)
helloActor ! "hello"
helloActor ! "tis a fine day for Akka"
//shutdown the actor system
system.terminate()
StdIn.readLine()
}
Where we have the following HelloActor
implementation:
import akka.actor.Actor
class HelloActor extends Actor {
def receive = {
case "hello" => println("world")
case _ => println("unknown message")
}
}
See how for this simple actor
we only ever deal with 2 things in the receive
method:
It is considered good practice to ensure that you handle the correct messages and deal with unknown messages too.
How Do We Wait For A Response From An Actor?
So we just saw a tell (fire and forget) but how about an ask. This is slightly harder but not much, we simply have to deal with the fact that a Future[T]
will be the result of an ask. There are numerous ways of dealing with that, assuming we have the following actor
:
import akka.actor.Actor
class AskActor extends Actor {
def receive = {
case GetDateMessage => sender ! new org.joda.time.DateTime().toDate().toString()
case _ => println("unknown message")
}
}
Here are some examples of how to deal with the result of the ask:
import akka.actor._
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.concurrent.duration._
import scala.language.postfixOps
import scala.io.StdIn
import scala.util.{Success, Failure}
import ExecutionContext.Implicits.global
object Demo extends App {
//create the actor system
val system = ActorSystem("HelloSystem")
// default Actor constructor
val askActor = system.actorOf(Props[AskActor], name = "askactor")
//send some messages to the AskActor, we want a response from it
// (1) this is one way to "ask" another actor for information
implicit val timeout = Timeout(5 seconds)
val future1 = askActor ? GetDateMessage
val result1 = Await.result(future1, timeout.duration).asInstanceOf[String]
println(s"result1=$result1")
// (2) a slightly different way to ask another actor for information
val future2: Future[String] = ask(askActor, GetDateMessage).mapTo[String]
val result2 = Await.result(future2, 5 second)
println(s"result2=$result2")
// (3) don't use blocking call at all, just use future callbacks
val future3: Future[String] = ask(askActor, GetDateMessage).mapTo[String]
future3 onComplete {
case Success(result3) => println(s"result3=$result3")
case Failure(t) => println("An error has occured: " + t.getMessage)
}
//shutdown the actor system
system.terminate()
StdIn.readLine()
}
Piping Futures
Another use case you may have is that you may want to use Future[T]
internally within the actor
code and send Future[T]
around from actor
to actor
. Akka also supports this by the use of the pipe pattern, which you can use like this where we are piping the Future[List[Int]]
back to the sender.
import akka.actor._
import akka.pattern.pipe
import scala.concurrent.{ExecutionContext, Future}
import ExecutionContext.Implicits.global
class FutureResultActor extends Actor {
def receive = {
case GetIdsFromDatabase => {
Future(List(1,2,3)).pipeTo(sender)
}
case _ => println("unknown message")
}
}
Where the code that initiated the sending of the GetIdsFromDatabase message looks like this (the sender in the code above):
import akka.actor._
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.concurrent.duration._
import scala.language.postfixOps
import scala.io.StdIn
import scala.util.{Success, Failure}
import ExecutionContext.Implicits.global
object Demo extends App {
//create the actor system
val system = ActorSystem("HelloSystem")
// default Actor constructor
val futureResultActor = system.actorOf(Props[FutureResultActor], name = "futureresultactor")
//send some messages to the FutureResultActor, we expect a Future back from it
val future4: Future[List[Int]] = ask(futureResultActor, GetIdsFromDatabase).mapTo[List[Int]]
future4 onComplete {
case Success(result4) => println(s"result4=$result4")
case Failure(t) => println("An error has occured: " + t.getMessage)
}
//shutdown the actor system
system.terminate()
StdIn.readLine()
}
Where is the Code?
As previously stated, all the code for this series will end up in this GitHub repo: