January 18, 2016 | Software Consultancy
Last time in this series I summarised all the Akka Persistence related improvements in Akka 2.4. Since then Akka 2.4.1 has been released with some additional bug fixes and improvements so perhaps now is a perfect time to pick up this mini-series and introduce some other new features included in Akka 2.4.x.
WRITTEN BY
I figured it’s about time to wrap up some unfinished blogging business, certainly before Akka team releases Akka 2.5. I’ve been rather busy and distracted lately to properly follow up on some of my previous posts (this is what happens when you are notoriously ambitious about how many blog posts you can produce while still working on client projects full time). Last time in this series I summarised all the Akka Persistence related improvements in Akka 2.4. Since then Akka 2.4.1 has been released with some additional bug fixes and improvements so perhaps now is a perfect time to pick up this mini-series and introduce some other new features included in Akka 2.4.x. Actually, initially I wanted to cover all remaining changes included in Akka 2.4 but after digging into Akka Typed it became apparent this single module deserves a dedicated blog post so here we are.
The next “new” addition to Akka 2.4 I wanted to introduce is Akka Typed. I say “new” because this experimental module is not the first attempt at creating a type-safe API for Akka. Previous attempts include Typed Actors and Typed Channels and the authors of Akka Typed module are quite open about the years of experimentation (not always successful) that has culminated with the module as can be found in Akka 2.4. Equally, the expectation is that it may take some more time for the type-safe API to mature up enough to replace the current one, centred around Actor
trait.
But what is all the fuss about? Akka toolkit has been around for some time now and seems to be doing rather well.
For anybody starting their journey with Akka it may not be the first thing on their mind to be bothered why every message that Actor can receive is untyped (instance of Any
). Perhaps at first it may seem like a rather convenient way to avoid worrying about types but just get on with sending and receiving messages. Unfortunately, the current API suffers from a few drawbacks, which are to a good degree associated with the lack of type safety:
Actor.receive
method which can lead to some messages not being handled and the compiler can’t assist hereFuture
) – calling sender()
method from another execution context is a common beginner’s mistakeA believe the other major reason to create a better API for Akka comes from the fact that the current API is not reflecting the Actor Model’s semantics as closely as it could. The most basic and succinct definition of an actor defines 3 actions that the actor can take in response to an incoming message:
One might argue that all the above rules are codified into the Actor
trait (like become()
and unbecome()
for changing behaviours) but perhaps not as explicitly or neatly as it’s achieved in the API proposed in Akka Typed module and certainly not in a type-safe manner.
By now you might be wondering how any of this translates to the actual code. If actors are not extending Actor
trait how would you define and create an actor? If all the message types need to be statically declared, how can you create a non-trivial message protocol that multiple actors can use while benefiting from type safety? Lastly, how can actor behaviours change in response to incoming messages without stacking them up with become()
and unbecome()
(actually, the stack approach has been always rather problematic and rolling back to previous behaviour could easily lead to unexpected outcome).
I reckon the example used in the Akka Typed documentation illustrates how the typed API would be used in a more realistic situation. Consider we are building a chat application where multiple users can join a chat room and broadcast messages to all chat room members. Both the chat room and a chat client can be modelled as actors. To make the application more interesting and realistic let’s assume that each user maintains a session with the server.
Below is the snippet containing the protocol used for communication between the chat room actor and the chat client actors.
import akka.typed._
import akka.typed.ScalaDSL._
import akka.typed.AskPattern._
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.concurrent.Await
sealed trait Command
final case class GetSession(screenName: String, replyTo: ActorRef[SessionEvent])
extends Command
sealed trait SessionEvent
final case class SessionGranted(handle: ActorRef[PostMessage]) extends SessionEvent
final case class SessionDenied(reason: String) extends SessionEvent
final case class MessagePosted(screenName: String, message: String) extends SessionEvent
final case class PostMessage(message: String)
Now, the first thing you might pick up from the code so far is how the entire message protocol is quite structured and strongly typed (rather than a random set of case classes/objects that you might be used to). Using sealed traits will make the compiler complain if you forget to handle any of the message types the behaviour of your actor should respond to (if you use the right type of behaviour, but more about it later).
The second difference compared with the standard Akka API that you might spot is the fact that ActorRef
is parameterised with the type of the message you can send to the actor which address it represents. Additionally, because in Akka Typed there is no sender()
reference available (prevents closing over unstable references) all actor addresses need to be made explicit in the message protocol – addressing has been brought into the domain of communication protocol where it belongs.
Perhaps the most significant change in the new API is around how actor behaviours are expressed and the fact that now the behaviour is modelled separately from the actor itself and passed in at creation time. The example below shows how actor behaviour could be defined for the chat room actor.
private final case class PostSessionMessage(screenName: String, message: String)
extends Command
val behavior: Behavior[GetSession] =
ContextAware[Command] { ctx ⇒
var sessions = List.empty[ActorRef[SessionEvent]]
Static {
case GetSession(screenName, client) ⇒
sessions ::= client
val wrapper = ctx.spawnAdapter {
p: PostMessage ⇒ PostSessionMessage(screenName, p.message)
}
client ! SessionGranted(wrapper)
case PostSessionMessage(screenName, message) ⇒
val mp = MessagePosted(screenName, message)
sessions foreach (_ ! mp)
}
}.narrow
A little cryptic at first perhaps, a Scala DSL provided by the Akka Typed module allows to define and compose static and dynamic (aka stateful actors) behaviours. Example above uses Static
behaviour type and ContextAware
behaviour decorator to deliver the functionality what previously would have been available in Actor
trait (good example you can fix any problem by introducing a layer of indirection). One additional thing to note in the snippet above is ActorContext.spawnAdapter()
method used to create a child actor that translates different message types and hence help creating a rich and type-safe message protocol.
Modelling the dynamic behaviour requires either a Partial
or a Total
behaviour type that takes a function (partial or total respectively) mapping incoming typed message to the new behaviour, including a few predefined behaviours provided by the DSL like Same
(behaviour doesn’t change) or Stopped
(results in an actor to be terminated). The snippet below implements a possible behaviour of the chat client.
import ChatRoom._
val gabbler: Behavior[SessionEvent] =
Total {
case SessionDenied(reason) ⇒
println(s"cannot start chat room session: $reason")
Stopped
case SessionGranted(handle) ⇒
handle ! PostMessage("Hello World!")
Same
case MessagePosted(screenName, message) ⇒
println(s"message has been posted by '$screenName': $message")
Stopped
}
Akka Typed embraces communication via passing messages and lifts lifecycle hooks previously being part of the Actor
trait to be handled as typed messages. DSL offers two dedicated behaviour types called Full
(taking a partial function) and FullTotal
(taking a total function) that include lifecycle messages among those exposed to the behaviour they compose. Lifecycle event specific messages are wrapped in a Sig
case class, whereas normal messages are wrapped in Msg
case class. Below you can see the behaviour of the main actor of the chat application responding to some lifecycle event messages.
val main: Behavior[Unit] =
Full {
case Sig(ctx, PreStart) ⇒
val chatRoom = ctx.spawn(Props(ChatRoom.behavior), "chatroom")
val gabblerRef = ctx.spawn(Props(gabbler), "gabbler")
ctx.watch(gabblerRef)
chatRoom ! GetSession("ol’ Gabbler", gabblerRef)
Same
case Sig(_, Terminated(ref)) ⇒
Stopped
}
val system = ActorSystem("ChatRoomDemo", Props(main))
Await.result(system.whenTerminated, 1.second)
As I was getting deeper into the codebase of Akka Typed I could not stop wondering about some of the design choices behind the API and accompanying DSL. I definitely agree with the general direction of the project but do have a few comments on the proposed API:
ActorContext
using ActorContextAware
decorator (same might be argued for actor’s own address, especially that now it needs to passed around explicitly). In general, I suspect there is a lot of work required to cleanly separate actors behaviour (including state management) and actor’s runtime or more transient properties (currently encompassed in ActorContext
).Full
vs FullTotal
as well as MessageOrSignal
trait indicate more work is needed to separate different concerns at the API level.FSM
(Finite State Machine) actors – ideally the new API should be expressive enough to provide FSM capabilities, although I could be asking for too much here.Actor
and by extension PersistentActor
traits gone, persistence concern needs to be incorporated into or supported by the typed API, ideally in a non-intrusive way.Akka Typed is the best to date attempt to bring type safety to Akka framework and certainly looks very promising. Key design decisions like separating actor’s behaviour into a separate concern as well as treating lifecycle events as messages will result in a less coupled and much safer programming model. The work is still very much in progress and I hope we will see further changes in upcoming Akka releases, eventually leading to a completely revamped API for Akka that will make using the framework easier and less error-prone by leveraging Scala’s type system. API/DSL development is one of the most satisfying (and challenging) areas in programming and I strongly encourage all you hakkers to have a stab at playing with Akka Typed and trying to improve it (you can start with any of the issues listed in Github).
I haven’t anticipated to spend as much time looking at Akka Typed but the fascination with API/DSL development got the better of me. There are two more areas I want to cover among many changes included in Akka 2.4: new Distributed Data module and ongoing improvements to Akka Cluster. Stay tuned.
This blog is written exclusively by the OpenCredo team. We do not accept external contributions.
Agile India 2022 – Systems Thinking for Happy Staff and Elated Customers
Watch Simon Copsey’s talk from the Agile India Conference on “Systems Thinking for Happy Staff and Elated Customers.”Lean-Agile Delivery & Coaching Network and Digital Transformation Meetup
Watch Simon Copsey’s talk from the Lean-Agile Delivery & Coaching Network and Digital Transformation Meetup on “Seeing Clearly in Complexity” where he explores the Current…When Your Product Teams Should Aim to be Inefficient – Part 2
Many businesses advocate for efficiency, but this is not always the right goal. In part one of this article, we explored how product teams can…