You've just created a simple Akka application! Now lets explore the code and make some changes.
In short, Akka is a toolkit and runtime for building highly concurrent, distributed, and fault-tolerant event-driven applications on the JVM. Akka can be used with both Java and Scala. One of the most powerful features of Akka is its Actor Model of concurrency, which you will learn more about in this tutorial.
Akka can be used from either Java or Scala and this tutorial has both Java and Scala sample code.
The java directory contains the Java source code HelloAkkaJava.java and the scala directory contains the Scala source code HelloAkkaScala.scala.
The sample in this tutorial is pretty simple; it consists of a Greeter Actor who holds
onto the latest defined greeting string and can respond to two actions; set a new greeting
string and return the latest greeting string.
Next let's get started.
An Actor does not have a public API in terms of methods that you can invoke. Instead its public
API is defined through messages that the actor handles. Messages can be of arbitrary
type (any subtype of Object in Java or Any in Scala). This means that we
can send boxed primitive values (such as String, Integer,
Boolean etc.) as messages or plain data structures like arrays and
collection types. However, since the messages are the Actor's public API, you should define
messages with good names and rich semantic and domain specific meaning, even if it's just wrapping
your data type. This will make it easier to use, understand and debug actor-based systems.
Now we want to define three different messages;
WhoToGreet redefines the new greetingGreet asks the Actor for latest greetingGreeting returns the latest greeting
Let's start by defining the messages in Java (we are putting them inside an outer HelloAkkaJava class,
containing our full sample). It is very important that the messages we create are immutable (meaning that
they cannot be changed), if not we run the risk of accidentally sharing state between two different Actors
which will violate the Actor Model. In this sample we will not use any remoting, but it is a good practice to
always mark your messages as Serializable since then you will not run in to any runtime issues if
you decide to scale out (on to multiple nodes) with Akka but forget to go back and reimplement your messages.
// Java code
public static class Greet implements Serializable {}
public static class WhoToGreet implements Serializable {
public final String who;
public WhoToGreet(String who) {
this.who = who;
}
}
public static class Greeting implements Serializable {
public final String message;
public Greeting(String message) {
this.message = message;
}
}
This is the way we would define the messages in Scala. In Scala case classes and case objects make excellent messages since they are immutable and have support for pattern matching, something we will take advantage of in the Actor when matching on the messages it has received. Another advantage case classes has is that they are marked as serializable by default.
// Scala code
case object Greet
case class WhoToGreet(who: String)
case class Greeting(message: String)
The Actor is the unit of execution in Akka. Actors are object-oriented in the sense that they encapsulate state and behavior, but they have much stronger isolation than regular objects in Java or Scala. The Actor model prevents sharing state between Actors and the only way to observe another actor's state is by sending it a message asking for it. Actors are extremely lightweight, they are only constrained by memory of which they consume only a few hundred bytes each — this means you can easily create millions of concurrent Actors in a single application. Their strong isolation principles together with the event-driven model (that we will talk about later on) and location transparency makes it easy to solve hard concurrency and scalability problems in an intuitive way.
You create an Actor in Java by defining a class that extends UntypedActor and implement the
onReceive method (in Scala you have to extend Actor trait and implement the
receive method). It is in the onReceive method that you define the behavior; how
the Actor should react to the different messages it receives. An Actor can have — and often has —
state. Accessing or mutating the internal state of an Actor is fully thread safe since protected by the Actor model.
So, let's now create a Greeter Actor with a single variable greeting as its state, holding on to the latest
defined greeting, and in its onReceive method let's add the behavior for how it should react upon receiving
the WhoToGreet and the Greet messages.
Let's start by creating our Actor in Java (you can find the code in the HelloAkkaJava.java file):
// Java code
public static class Greeter extends UntypedActor {
String greeting = "";
public void onReceive(Object message) {
if (message instanceof WhoToGreet)
greeting = "hello, " + ((WhoToGreet) message).who;
else if (message instanceof Greet)
getSender().tell(new Greeting(greeting), getSelf());
else unhandled(message);
}
}
Actors like this one are “untyped” in the sense that the type of message received is not restricted—it is
Object as shown above. There are also typed actors, but we will
not concern ourselves with those now, the normal actors are the untyped ones.
Don't worry about the getSender(), tell(..) and getSelf() API calls,
we will get to that soon when we talk about sending and replying to messages.
Now let's implement it in Scala. As you can see, Scala's pattern matching features really simplify working with Actors, but apart from that it's pretty similar to the Java version (you can find the code in the HelloAkkaScala.scala file).
// Scala code
class Greeter extends Actor {
var greeting = ""
def receive = {
case WhoToGreet(who) => greeting = s"hello, $who"
case Greet => sender ! Greeting(greeting)
}
}
You will notice one difference to the Java version: here we do not
explicitly pass unhandled messages to the unhandled() method.
This is not necessary since the behavior defined by the
receive method is expressed as a so-called partial
function, which means that all messages for which no matching
case statement is written down will be recognized as being not
handled and Akka will automatically pass them to the
unhandled() method for you.
Another difference is that the trait from which the Scala actor inherits is just called
Actor. This is the Scala API while UntypedActor is the Java API
for the same kind of actor.
So far we have defined our Actor and its messages. Now let's create an instance of this actor. In Akka you can't
create an instance of an Actor the regular way using new, instead you create it using a factory. What is
returned from this factory is not an instance of the Actor itself but an ActorRef pointing to our actor
instance.
This level of indirection adds a lot of power and flexibility. It enables for example location transparency
meaning that the ActorRef can, while retaining the same semantics, represent an instance of the running actor
in-process or on a remote machine. I.e. location doesn't matter. This also means that the runtime can if needed
optimize the system by changing an actor's location or the application's topology while it is running. Another
thing that this level of indirection enables is the "let it crash" model of failure management in which the
system can heal itself by crashing and restarting faulty actors.
This factory in Akka is the ActorSystem and is to some extent similar to Spring's BeanFactory
in that it also acts as a container for your Actors, managing their life-cycles etc. You create an Actor through the
actorOf factory method. This method takes a configuration object called Props and a name.
Actor (and ActorSystem) names are important in Akka, you use them for example when looking Actors up as well as when you
configure them in the configuration
file, so you should take your time giving your Actors good names.
This is the code that we have to write in Java:
// Java code
final ActorSystem system = ActorSystem.create("helloakka");
final ActorRef greeter = system.actorOf(Props.create(Greeter.class), "greeter");
The Scala code is not much different:
// Scala code
val system = ActorSystem("helloakka")
val greeter = system.actorOf(Props[Greeter], "greeter")
Now we have a running instance of a Greeter actor. Next we will learn how to communicate with it.
All communication with Actors is done through asynchronous message passing. This is what makes Actors reactive and event driven. An Actor doesn't do anything unless it's been told to do something, and you tell it to do something by sending the message. Sending a message asynchronously means that the sender does not stick around waiting for the message to be processed by the recipient actor. Instead the Actor hands the message off by putting it on the recipient's mailbox and is then free to do something more important than waiting for the recipient to react on the message. The actor's mailbox is essentially a message queue and has ordering semantics, this guarantees that the ordering of multiple messages sent from the same Actor is preserved, while they can be interleaved with the messages sent by another actor.
You might be wondering what the Actor is doing when it is not processing messages, i.e. doing actual work? It is in a suspended state in which it does not consume any resources apart from memory.
You tell an Actor to do something by passing in a message into the tell method on the
ActorRef. This method puts the message on the actor's mailbox and then returns immediately.
// Java code
greeter.tell(new WhoToGreet("akka"), ActorRef.noSender());
// Scala code
greeter.tell(WhoToGreet("akka"), ActorRef.noSender)
If you are using Akka from Scala then you can also use the alias; !, called the bang operator.
// Scala code
greeter ! WhoToGreet("akka")
Sometimes the communication pattern is not just a one-way style of communication but instead lends
itself towards request-reply. One explicit way of doing that is by adding a reference of yourself as part
of the message so the receiver can use that reference to send a reply back to you. This is such a common
scenario that it is directly supported by Akka; for every message you send you have the option of passing
along the sender reference (the Actor's ActorRef). If you are sending a message from within
an Actor then you have access to your own ActorRef through self reference, please
note that you should never use this. In Java you can access the self reference through the
getSelf() method, while you can use self in Scala.
// Java code
// From within an Actor
greeter.tell(new Greet(), getSelf());
In Scala this is simplified a bit. Scala has something called implicit parameters which allows you to automatically and transparently pass in parameters into methods, a feature that we are taking advantage of here to automatically pass along the sender reference when you send a message to another Actor.
This code will, if invoked from within Actor 'A' automatically pass along the ActorRef of
Actor 'A' as the sender of this message:
// Scala code
// From within an Actor
greeter ! Greet
If you choose not to pass in a sender reference into the tell method, or forget it, then a reference to
the 'dead-letter' Actor will be used. The 'dead-letter' Actor is where all unhandled messages end up, and
you can use Akka's Event Bus to subscribe on them.
This sender reference will then be available in the receiver Actor when it's processing the message. Since
each message is paired with its unique sender reference, the "current" sender reference will change with each
new message you process. So if you for some reason need to use a specific sender reference later then you have
to hold on to it, storing it away in a member field or similar. In Java you can access it using the
getSender() method (while you can use sender in Scala).
// Java code
// From within the Greeter Actor
getSender().tell(new Greeting(greeting), getSelf());
// Scala code
// From within the Greeter Actor
sender ! Greeting(greeting)
Most real-world Actor applications make use of more than one Actor. The inventor of the Actor Model, Carl Hewitt, recently said in an interview that; "One Actor is no Actor. Actors come in systems." This is important wisdom. To truly leverage the Actor Model you should use lots of Actors. Every hard problem in Actor programming can be solved by adding more Actors; by breaking down the problem into subtasks and delegate by handing them to new Actors.
However, for simplicity we are just using a single Actor in this sample. This means that if we communicate
with this single actor from a main program then we have no sender, since we are
not communicating with the Actor from within another Actor. Luckily Akka has a nice solution to this problem;
Inbox.
Inbox allows you to create an "actor-in-a-box", i.e. it contains an Actor which can be used as a puppet
for sending messages to other Actors and receiving their replies. You can create an Inbox using
Inbox.create and send messages from it using inbox.send. The internal Actor will just
put any message it receives into a queue from which it can be retrieved by calling inbox.receive;
if the queue is empty then that call will block until a message becomes available. Pretty simple.
As you probably know, blocking is something that can really inhibit performance and scalability, and that you should use very sparingly and with care. That said, we are making use of it in this sample since it simplifies the sample and makes it very easy to follow the message flow.
Now let's complete this tutorial by writing the driver code that will exercise our Greeter Actor.
// Java code
// Create an "actor-in-a-box"
final Inbox inbox = Inbox.create(system);
// Tell the 'greeter' to change its 'greeting' message
greeter.tell(new WhoToGreet("akka"), ActorRef.noSender());
// Ask the 'greeter for the latest 'greeting'
// Reply should go to the mailbox
inbox.send(greeter, new Greet());
// Wait 5 seconds for the reply with the 'greeting' message
Greeting greeting = (Greeting) inbox.receive(Duration.create(5, "seconds"));
System.out.println("Greeting: " + greeting.message);
// Scala code
// Create an "actor-in-a-box"
val inbox = Inbox.create(system)
// Tell the 'greeter' to change its 'greeting' message
greeter tell WhoToGreet("akka")
// Ask the 'greeter for the latest 'greeting'
// Reply should go to the mailbox
inbox.send(greeter, Greet)
// Wait 5 seconds for the reply with the 'greeting' message
val Greeting(message) = inbox.receive(5.seconds)
println(s"Greeting: $message")
Both the Java and Scala examples each have unit tests in the test directory. The Java tests use JUnit, while the Scala tests use ScalaTest.
Both of these tests are making use of the excellent Akka TestKit module, which makes it so much easier to test and verify concurrent code.
Making changes to the sample's source or the test source will cause the tests to be automatically re-run.
Congratulations!!!
Now you have almost completed the tutorial and written a simple Akka application. If you have not looked at the full sample then now is a good time to do so.
Let's have some fun and run it.
In Run, select the application to run from the drop-down list, and select Start. Feel free to modify, compile and re-run the sample.
Use Inspect to see what is happening with the Actors in a running application. This will contain a list of the Actors in the Actor System. Drilling down into an individual Actor will display a number of statistics and information about the Actor. Deviations will show you any issues with your Actors.
The Akka Documentation explains each one of the topics covered in this tutorial in great depth, and much much more.
The manual is split up into Java and Scala versions.
The Akka Team blog; Let It Crash has a lot of articles and
can be a good additional source of information.
If you have questions don't hesitate to post them to the
akka-user Google Group.