Tour of Akka Typed: Protocols and Behaviors

Update 18.08.2019: corrected actor hierarchy and default supervision strategy

Ukrainian translation / Chinese translation

In this series we are going to explore Akka Typed, the new Akka Actor API that brings significant advantage over the classic one. Akka Typed is ready for production since April and although the API is still marked as may change I think it is a good time to look into it and to learn what’s new.

If you’re not familiar with the Akka Actor API, don’t worry — this series is intended to be understandable even if you do not. If you are familiar with the Actor API then this series will help you understand and learn how to work with Akka Typed.

Why Akka Typed

The actor model has proven itself to be a powerful abstraction when it comes to build real-world, fault-tolerant, concurrent and distributed systems. It is based on the notion of message being passed between individual actors that understand these messages and react to them. Fault-tolerance is provided via hierarchical supervision: actors can create child actors which they monitor for failure an can restart or recreate if necessary, such that parts of an actor system (or the system as a whole) are capable of healing themselves after crashes.

The classic Akka actor API embodies these principles by providing a very simple set of methods to process incoming messages, send messages and create children:

This model and API have quite an advantage over threads: the handling of messages by an actor is guaranteed to happen sequentially, the state of an actor can only be altered by the actor itself and as such it is possible to reason about what happens much more easily than with shared state accessed concurrently.

At the same time, there are a few limitations to this API, some of the most common ones being described in the Akka anti-patterns series.

The main issue – and I’ve seen this being the case in quite a few larger Akka projects over the years – is that the API is making it difficult to scale and maintain larger actor systems as they grow. This is because the API does not enforce a “protocol-first” approach. Indeed, one of the first things you learn about in the introductory Akka training course is to clearly define your protocol in terms of handled messages as well as to use the full path (OrderProcessor.ProcessOrder) to access messages. Coming back to the example above, this is how the protocol of the OrderProcessor actor looks like:

That is, this best practice will only get you that far: even if you carefully use it everywhere, there is still one major problem. Indeed, nothing prevents you from sending a message to an actor that cannot handle it. An actor’s receive method will accept any message, and the default behaviour will be to pass those unhandled messages to the unhandled method of an actor, which logs them by default — if the logging of unhandled messages is configured correctly. This can be the source of extreme frustration to newcomers as you litteraly can’t see anything going wrong and yet your system does not work.

Going one step further, there is no supporting mechanism to help you with evolutions of the protocl over time. This is to say that introducing new messages may provide to be quite difficult as it is always possible to forget their processing in one place or another. Tests will help, of course, but unless you setup advanced log filters for tests that verify that no unhandled messages have been logged, you’re still at risk of missing one place or two.

This is where the Akka Typed API comes in. This API is designed to be “protocol-first”: you no longer have a choice but to spend at least a little bit of time thinking about the messages each actor can deal with. Unlike the classic API where following this best practice is optional, you need to formalize the set of handled messages during implementation.

There’s one thing I’d like to stress at this point, after having seen a fair share of real-world Akka systems: the intent of Akka Typed is not merely to make sure that messages get declared in a structured way and to ensure that a few unhandled messages aren’t missed. Instead its aim is to lead you to really think about the system design upfront. With the right set of actors at the right granularity communicting with the right message patterns, it is possible to build very powerful systems that are, at their core, still quite simple — or, should I say, as simple as the domain permits. Unfortunately what I’ve observed many times over is that people tend to overdo the “many actors, many messages” part and end up with unnecessary complexity which is hard to get rid of afterhand. As Martin Thompson says:

Let’s build a payment processor

For this article series we’re yet again going to use the example of a payment processor (as in the Tour of Akka Cluster series – this domain just never gets old (and also I happen to have quite a bit of experience with it, Akka being well-suited for building high-volume, low-latency transaction systems).

Our Payment Processor is capable of handling payments for multiple types of payment methods: various credit cards (Visa, Master Card, American Express, etc.), SEPA payments, Apple Pay, Google Pay, Amazon Pay, PayPal – you name it. It supports a variety of payment flows with different validation mechanisms, recurring payments and many more options of the like.

In order to deal with such a versatilty in business requirements, our system is divided in several components so as to make it possible to easily add new payment methods:

The initial components of the Payment Processor
  • API: this is the entry point to our payment processor. It handles authentication, supports multiple formats and dispatches the request to the right downstream components. For the purpose of this article series, we’re going to keep the implementation very simple.
  • Payment Handler: this key component understands the core payment requests. Based on the information that it retrieves from the configuration component, it then orchestrates the handling of a payment request which can have several steps (validation, execution, etc.).
  • Configuration: this component stores the configuration associated with API users as well as with the entities allowed to request payments (in domain slang, that’s a merchant).
  • Payment processors: this family of components is responsible for executing payments. In our example we will only feature a simple Credit Card Payment Processor, in a real system there’d be many more of these components. The processors also typically communicate with more downstream components or third-party systems but for the sake of complexity we will not show any of this here.

Note that in a real system there’d be more concerns than modeled here – for example, we don’t talk about registering payment methods with our processor at all – but for the purpose of introducing Akka Typed, this should do.

Protocols in Akka Typed

As explained at length earlier on, protocols matter, and Akka Typed makes it possible to express them (or at least, to some extent — to the very least, much more so than with the classic Akka API).

“But what’s a protocol?”, you ask. “Isn’t that just messages?”. There’s a little bit more to it. To put it simply, I would define a protocol as a set of messages exchanged between two ore more parties in a particular order and combination. There’s a variety of protocol families (check out the OSI model), you are likely familiar with famous protocols such as TCP or HTTPS. In our case, we’re operating at the application layer. You can think of protocols as APIs on steroids: whilst APIs only describe individual calls (including parameters, request contents and response contents), protocols describe how calls interact with one another in order to reach a desired target state of the systems in communication.

In the Typed Actor API, protocols can be expressed in terms of classes and typed actor references. Let’s take the example of a simple protocol that lets us retrieve configuration data from the configuration component:

This example follows the request-response message pattern. To learn more about message patterns (and reactive system design in general) check out the Reactive Design Patterns book.

If you have already used the classic Akka actor API, you will notice two major changes to how this pattern is implemented. First off, the sender address is included in the message definition. In the classic API this was handled transparently by Akka, which was capturing the actor reference of the sender of a message and allowed to reply to it by sending a message to the sender(). Second, and most importantly, the ActorRef is now typed: it refers to a particular type of message that the sender can understand. In our case, we are using traits (such as the ConfigurationResponse trait) in order to allow the sender to deal with more than one type of response.

In order to understand why this matters and how this key change enables Akka Typed applications to be safer and easier to evolve than the classic variant, we need to have a look at the Actor definition. Say we wanted to implement the Configuration actor. One way of doing it would be the following:

What you notice here is that we’re defining a class that inherits from the AbstractBehavior trait, which takes a type parameter. This way, we declare that the Configuration actor is only understanding messages of type ConfigurationMessage — in other words, this makes it possible for the compiler to statically check whether the recipient of a message can indeed handle the message it is being sent.

Note that in the example above we are using the object-oriented style of declaring an actor in this example – we’ll look at the functional style later.

Implementing our first typed actor

We’ll start with building out a fairly simple version of the Configuration component which should be capable of retrieving (and later also storing) merchant configuration. We’ll continue using the object-oriented style as we started using it earlier — if you have used the classic actor API before this style should be fairly familiar.

Extending the AbstractBehavior trait requires us to implement the onMessage method which returns a Behavior:

At first sight, this implementation looks fairly similar to the example of the classic actor API in the beginning of this article. We override a message and match the message we receive and then do something as a result.

The difference here is that we now return a Behavior. The behavior of an actor when it receives a message is defined by Hewitt et al to be one or more of the following actions:

  • it can send one or more messages to other actors
  • it can create new child actors
  • it can specify a (possibly different) behavior to be applied to the next message

In the Akka Typed API, a Behavior is both responsible for processing a message as well as for indicating how the next message should be handled, which it can do so by returning a Behavior. If nothing changes (as in the example above) the same Behavior can be returned (which is why we return this here, which in the case of the object-oriented API makes sense as the instance of the actor class implements a Behavior).

We will talk more about Behaviors throughout the series. They’re one of the essential building blocks of Akka Typed and as we will see later, they can easily be composed and tested.

Talking about testing, the actor implemented above can easily be tested using the Typed Akka TestKit. In combination with ScalaTest, this is how a test case can be setup:

Supervising and starting the actor

Actors can’t run alone in isolation — they’re part of an Actor System which is the environment which allocates the resources and provides the overall infrastructure for actors to exist and to interact.

Within the Akka Actor System, each actor is the child of another actor. The actor at the very top of that hierarchy is called the root guardian (/), its direct descendants are the user guardian (/user) for actors created in userspace and the system guardian (/system) for actors created and managed by Akka. Therefore the path of all the actors that we’ll be creating starts by /user.

With Akka Typed there is an important difference to the classic API as to how the user guardian is handled. In the classic API, Akka provides a user guardian actor. In the Typed API, it is up to the user to provide the behavior of the user guardian. In other words, it is our job as application developer to implement the behavior of the user guardian and think a little about how it should behave.

In order to create our Configuration actor, we could just go ahead and pass it to the ActorSystem to act as our user guardian, but that wouldn’t make a lot of sense given that we also want to create other actors and there’s no reason that the Configuration actor should supervise them all. Furthermore, in the actor model, parental supervision (i.e. the hierarchy of actors) goes hand in hand with failure handling: the parent of an actor is responsible for deciding what to do should a child actor crash (which it does if it throws an exception), and therefore the grouping of actors directly influences how crashes can be managed. As such we should be using a dedicated parent actor that deals with supervising the actors of our application so that it can also decide how to deal with the failure of its child actors. With the Akka Typed API, the default supervision strategy is to stop a failing child actor (this, too, is an important difference from the classic API, where actors would get restarted). With our own user guardian actor we can react differently to different types of failure causes (exceptions) and make different decisions as to how the failure should be handled. Therefore we will introduce a PaymentProcessor actor that will be the parent of all the component actors we will create and that will be acting as the user guardian. In terms of actor paths, this is how our simple hierarchy will look like:

The Actor System – user-defined actors are in green

The PaymentProcessor actor in itself doesn’t have much to do, except for creating the child Configuration actor when it is started. It has no state and does not need to handle messages. We’ll use the functional style of the Typed Actor API to implement it so instead of extending a trait we create a function that returns a Behavior:

The Behaviors.setup method is the entry point for creating Behaviors. It provides an ActorContext which we’ll use to log the fact that the processor has started and to create the first Configuration child actor via the spawn method. If you’re not familiar with Scala, the first argument passed to the method will call the apply() method of the Configuration companion object (see below) which itself exposes a Behavior. The second argument is the name of the actor, which is also used in the path (/user/config).

Notice that we call setup[Nothing] — indeed, the PaymentProcessor actor is excepted to handle no message.

In order to spawn a Configuration child actor, we need a Behavior. Passing in a new instance of the Configuration class itself, which is a subclass of AbstractBehavior won’t do — we need to encapsulate that Behavior in the setup Behavior like so:

Now that the supervisor is in place, all we need in order to start things up is an ActorSystem which we’ll instruct to create our user guardian actor. Akka provides a factory method of the ActorSystem that expects to be passed a user guardian behavior:

And that’s it! If we now run our application, we’ll see the PaymentProcessor starting:

In order to see the Configuration actor do something we will need another actor for it to interact with, which we’ll create in the next article of the series.

Concept comparison table

At the end of each article we’ll have a little table that attempts to map familiar concepts from the classic API to the Typed API (this isn’t meant to be a strict mapping, think of it more as a quick way to be able to relate to the Akka Typed API if you are familiar with the classic Akka Actor API).

Here’s the one from this article (see also the official migration guide):

extends Actorextends AbstractBehavior[T] (object-oriented style)
/user guardian is provided by Akka/user guardian is provided by users, passed as a Behavior to the ActorSystem
default supervision strategy decision: restart child actor on failuredefault supervision strategy decision: stop child actor on crash

This is it for this first part of exploring the Akka Typed API! You can find the source code of this article here.

Go to part 2 of this series

Comments 21

  1. Pingback: Tour of Akka Typed: Message Adapters, Ask Pattern and Actor Discovery - manuel bernhardt

  2. Thanks for the great article, Manuel.
    It seems that there is a typo in the Concept comparison table: “extends AbstractActor” probably should be “extends AbstractBehavior[T]”.
    Also wouldn’t it be a better fit to map Props as a type to Behavior[T], and Behaviors.setup to Props.apply ?

    1. Post

      Hi Paul, thanks for the feedback! I wouldn’t map Props to Behavior[T] given that in Akka Classic, the job of Props is to act as a (serializable) factory for actors that allows Akka to create new instances of an Actor when it needs to – and that’s what Behaviors.setup does in Akka Typed.

      1. Hi, I still have a question about the comparison between
        extends Actor extends AbstractActor[T] (object-oriented style)
        In Akka Typed doesn’t extends Actor become extends AbstractActor[T] (object-oriented style)

        I think Paul asked the question but I wasn’t sure if your reply covered it.

        Thanks again for you articles and videos!

        1. Post
  3. Pingback: Tour of Akka Typed: supervision and signals - manuel bernhardt

  4. Thanks for the article.

    A general question – if i have to write hundred lines of code in the actor for a business logic implementation, is it a good practice to
    (1) write them outside case statement within the actor class
    (2) within the case ?

    // the Actor trait (AbstractActor class in Java) is the entry point for using the API
    class OrderProcessor extends Actor {

    // the receive method is where messages are processed
    def receive: Receive = {
    case order @ OrderProcessor.ProcessOrder =>
    // the actorOf method spawns a new child of the invoking actor
    // business logic — here — call a function here …. ?
    val connection = context.actorOf(BankConnection.props(order.bankIdentifier)
    // the ! method stands for send (in a fire-and-forget fashion)
    connection ! BankConnection.ExecuteOrder(order)

    // business logic function here ?? with return data to actor ?

    1. Post

      As a rule of thumb I’d keep the code inside of the receive handlers (both in classic and in typed) as lean as possible, and just call functions from therein. Sometimes there’s so little to do that processing things right away inside of the handlers is okay, but when things grow I’d just create well-named functions. Those can be kept inside of the actor itself, but of course nothing prevents you from implementing complex business logic in completely separate classes that are completely agnostic to Akka. Just make sure those don’t do funny things like creating threads or blocking.

    1. Post
  5. I am trying to follow along with the code snippet and I noticed some errors (or atleast I think they are). Perhaps you can check, confirm and edit the post appropriately?

    Here they are:

    In the section where the protocol is defined, you have Configuration extending ConfigurationResponse. But in the Implementation of Configuration actor, you have it as ConfigurationFound and not Configuration
    In your implementation of Configuration you did not pass the context to AbstractBehavior. For example you had:

    class Configuration(context: ActorContext[ConfigurationMessage]) extends AbstractBehavior[ConfigurationMessage]

    I believe that should be:

    class Configuration(context: ActorContext[ConfigurationMessage]) extends AbstractBehaviorConfigurationMessage

    You did not include the definition of MerchantId and MerchantConfiguration in the code snippets.
    Would be nice to also include the imports 🙂 ActorContext had like four different variant. I had to try all to figure out the one that would compile.

    1. Post
  6. Pingback: Tour of Akka Typed: Routers, Cluster Singleton and Cluster Sharding - manuel bernhardt

  7. Pingback: Tour of Akka Typed: Event Sourcing - manuel bernhardt

    1. Post
  8. Great article Manuel, congrats! 🙂

    I was trying to run the example you provided, and there’s a piece of configuration missing at application.conf: = cluster

    Without it, it throws an exception on application startup.

    1. Post
  9. Pingback: Tour of Akka Typed: Cluster Singleton and Routers - manuel bernhardt

  10. Hi!

    Thanks for all these interesting articles. Here is a case where I cannot find a clean solution when trying to share some implementations with typed actors.

    I have two types of Actors, say ChildA and ChildB, which share part of their behaviours, thus I want those implementations to be shared in an abstract class or a trait which could be extended by both types of Actors. This way, given the Message hierarchy:
    sealed trait Message
    final case class CommonMessage

    The onMessage method for the parent class could look like this:
    override def onMessage(msg: Message): Behavior[Message] = {
    case CommonMessage => …

    ChildA could look like this:
    override def onMessage(msg: Message): Behavior[Message] = super.onMessage orElse {
    case m: SpecificMessageForChildA => …
    And the same for ChildB.

    My problem is that since Behavior, Context and ActorRef are typed to Message and not co-variant – even if they would be, what about the Java API? The trait Message has to be directly extended by all SpecificMessageForChild*. Which means that in a way, the parent class is aware of all the messages its child could receive and I find it quite annoying.

    I feel like there is either a design mistake in what I am trying to do or a lack of modularity in the design for typed actors. What are your thoughts?

    Thanks a lot,

  11. Appreciate you taking time out and writing a nice article comparing typed with classic API. I have used classic API in the past and didn’t have any trouble understanding and running the examples. This should be part of akka docs 🙂

Leave a Reply

Your email address will not be published.