What is Akka?
Akka is a framework for writing message driven, scalable, error resilient applications, while making working with concurrency easy. This is achieved by modeling applications around the concept of “actors”.
Actors are entities that communicate with each other through messages. When an actor receives a message, it can react by
- changing its internal state,
- creating new actors, and
- sending out messages to actors.
Multiple actors typically run in parallel, but the business logic within each actor is always executed single threaded, thereby avoiding complications that normally arise from concurrency.
Akka Typed
However, until the advent of Akka Typed, messages passed between actors have been untyped: You have been able to send any kind of message to any actor without the compiler being able to assert if the receiving actor is able to handle the message. This prevented detecting errors during compilation and made reasoning about actor driven applications difficult at times.
With Akka Typed, you can now specify which message types an actor is able to handle and the compiler will complain about mismatching message types sent to an actor.
An Example
The following is an example that shows the new Akka typed API in action (you can view and download the complete source here):
package dev.oreineke.akka.typed.example
import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
object Application extends App {
// 1) define the message type "Message":
sealed trait Message
final case class Greet(name: String) extends Message
final object Stop extends Message
// 2) define the actor behavior (inluding the message type "Message" it can handle!):
val readyToGreet = Behaviors.receive[Message] { (context, message) =>
message match {
case Greet(name) =>
context.log.info(s"Hello $name!!!")
Behaviors.same
case Stop =>
context.log.info("Goodbye!")
Behaviors.stopped
}
}
// 3) create the actor:
val greeter = ActorSystem(readyToGreet, "Greeter")
// 4) send a few messages to the actor:
greeter ! Greet("Peter")
greeter ! Greet("Paul")
greeter ! Greet("Marry")
greeter ! Stop
// 5) the following would already fail during compilation
// greeter ! "Eat this"
}
Explanation
After defining the message type the actor is willing to accept in the Message
trait,
sealed trait Message
final case class Greet(name: String) extends Message
final object Stop extends Message
an actor Behavior
named readyToGreet
is defined:
val readyToGreet = Behaviors.receive[Message] { (context, message) =>
message match {
case Greet(name) =>
context.log.info(s"Hello $name!!!")
Behaviors.same
case Stop =>
context.log.info("Goodbye!")
Behaviors.stopped
}
}
Note that, to define the actor behavior readyToGreet
, a typed factory method Behaviors.receive[Message]
is used. It expects a function mapping the received message of type Message
to a new Behavior
. For the returned Behavior
, instead of defining new behaviors, predefined factory methods are used to generate special return values: Behaviors.same
to cause the current behavior to stay active, and Behavior.stop
to cause the actor to die.
Finally, ActorSystem
is called to create an actor with the Behavior
readyToGreet
(along with a name for easy actor identification):
val greeter = ActorSystem(readyToGreet, "Greeter")
At this point, the actor greeter
is ready to receive massages of type Message
:
greeter ! Greet("Peter")
greeter ! Greet("Paul")
greeter ! Greet("Marry")
greeter ! Stop
If we would have tried to send a message of type String
to the actor, the compiler would have noticed the error:
greeter ! "Eat this" // this will fail during compilation