Tour of Akka Typed: Message Adapters, Ask Pattern and Actor Discovery

Published on 7 August 2019 , last updated on 6 September 2019

Note: I’ll be giving a training course on Akka Typed in December in Vienna

In the previous article of this series we’ve explored the basics of the Akka Typed API: why it was created and what are its benefits in comparison to the classic Actor API, how to build typed actor systems via protocols and behaviors and how to create typed actors. In this series we’re going to go further down the route of building typed actor systems by looking at fundamental concepts necessary for the interaction between actors.

Let us start by defining the centerpiece actor of this system: the PaymentHandler. This is the actor that is responsible for handling the requests that are received by the API by retrieving the necessary configuration and then orchestrating the handling through the adequate components.

The Payment request flow

This time around we will be using the functional style of the typed API to create the actor.

The API will send us a HandlePayment message upon which we can retrieve the necessary configuration from our Configuration actor:

Message flow for a payment request

If you still have the first article of this series in mind, you may have anticipated that at this point there will be an issue with our protocol definition and the use of the typed actor API. Let’s have a quick look at the message definitions:

Indeed, the PaymentHandling actor does not have the knowledge of the response messages of Configuration – and in all fairness, it shouldn’t. I’ve always found this aspect of using the classic Akka API with message patterns of type Request-Response / Command-Event to be a bit cumbersome as I find it to be perfectly legitimate to define the response message types (or events, when you’re using that semantic) close to the actor that emits the messages but unfortunately this means that the actors that receive a “foreign” message will not have it as part of their own protocol. This in turn leads to the protocol definition in classic Akka API systems being incomplete, or should I say scattered, in the sense that the message protocols of one actor will always miss some messages sent by other actors.

So how do we work with this in the Akka Typed API? The answer is that those cases need to be made explicit with the use of Adapted Responses.

Adapted Responses

In order for PaymentHandling to consume responses of the Configuration actor we’ll need two things:

Since in our case there are several possible response messages from Configuration, it would not be very practical to redefine all of them as part of the payment handling protocol. A simple mechanism is to use a wrapper like so:

Let’s now go ahead and implement the behavior of PaymentHandling using adapted responses:

The part that’s most interesting to us here is the configurationResponseAdapter:

This is the mechanism by which we can turn an ActorRef[PaymentHandlingMessage] into an ActorRef[ConfigurationResponse], thus allowing the Configuration actor to reply to the PaymentHandling actor and to translate the messages transparently. As shown here, using a wrapper to this effect makes sense when there are several possible responses to translate – for simpler cases, a direct mapping without a wrapper may be enough.

Since we are using the functional style of the Typed Actor API here, the state of the actor is not kept in a mutable data structure but instead passed down from one behavior to the next by virtue of the function definition:

When returning this behavior, the state can now be altered by calling handle with different value for the request map.

Note that the choice of using of a Map[MerchantId, HandlePayment] is a pretty poor one which wouldn’t work in real life (and which I only took to keep the example simple): as soon as subsequent HandlePayment messages with the same merchantId are received, chances are that the first values would be overwritten. There are several correct solutions for this:

Let’s have a look at the ask pattern in more detail which also allows us to map the response.

The ask pattern

The ask pattern allows two actors to interact in such a way that there’s a 1 to 1 mapping between the request and the response. Since there’s no guarantee that the responding actor will in fact ever respond, the ask pattern requires to define a timeout after which the interaction is considered to fail. What happens under the hood is that a TimeoutException is thrown.

In the following implementation of the PaymentHandling we no longer keep track of the in-flight payment requests as there’s now a direct mapping for the interactions with the configuration service. Instead we extend the internal protocol of the PaymentHandling actor to carry the information we require:

Our actor implementation now becomes:

Again the really interesting bit of code here is the invocation of ask, which for the Scala API is a function with 4 parameter lists (3 explicit and one implicit):

Whilst this method signature might look a bit cumbersome at first, I think that it is a really good move on the part of the Akka team as it entirely eliminates a source of mistakes related to ask returning a Future in the classic Actor API, which made it possible to mistakenly close over mutable state of the actor.

As we now have retrieved the configuration we can proceed to contacting the right payment processor and ask it to perform the payment. For this purpose, let’s have a look at actor discovery.

Discovering actors with the Receptionist

With a name that could have been given to a Matrix character, The Receptionist allows you to get typed actor references given a key. If you’re familiar with the classic Actor API then this is what replaces the ActorSelection.

Edgar Poe in the series Altered Carbon sure is a committed Receptionist

The way in which the discovery mechanism works is pretty straight-forward — it simply acts as a registry:

In our example, we don’t want to have to query the whereabouts of our processor actors for every request, therefore we’ll be using the subscription mechanism instead.

Let’s start by fleshing out a really simply processor and registering it with the Receptionist when it starts up. To make the processors even more pluggable, they will share the same protocol:

We can now start with scaffolding the first payment processor for credit cards:

As described earlier on, this process is quite simple: whenever the CreditCardProcessor is started it will register its actor reference with the Receptionist.

Note that the ServiceKey takes a type parameter, which is supposed to be the type of the protocol understood by the registered actor.

Next, we need to subscribe the PaymentHandling actor to updates of the Receptionist so that we are made aware of all the available processors:

Note that since the Receptionist will return a Listing message, we need to use a message adapter coupled with an internal protocol message (ProcessorReference) to be able to understand the update.

At this point we now have a set of Listing‘s at our disposal which we can use to send off the message to the right processor using the configuration (this step isn’t shown in the example, but you can imagine that given the right configuration this should be rather simple).

Concept comparison table

ask / ?context.ask

And this is it for the second article of this series! You can find the source code of this article here.

Liked this article? Subscribe to the mailing list to get regular updates on similar topics.

Leave a Reply