Designing reactive actors | Akka.NET

with 4 Comments

ActorRef.Ask might be a code smell

The Ask operator on an IActorRef allows you to send a message to an actor or actor selection and then await for a response.
This is useful if you want to design some sort of workflow that send queries to one or more actors.

However, this might be a sign of bad design, because when designing a reactive system, you want information to flow in your direction, you should not need to request the information.

So if you find yourself designing actors that are Ask‘ing for information, this could be sign that your actor should instead be subscribing to events from the source that you are Ask‘ing.

a Pull based design

For example, let’s say that we are designing a game;
When the player enters a room, we need to know what other players and objects that are present in this room.

We could design this as a query, where the player actor asks the room; “what objects are in here?” and the room could reply to that request.
This is absolutely a viable design, but you might end up designing a lot of complex asynchronous workflows.

  1. Ask the room what objects are present
  2. Ask each of the objects what their name (or other state) is
  3. Aggregate the result from all the objects in the room
  4. Present the result to the player

This is a fairly complex flow of events.
You need to have some sort of aggregator that can query and collect the responses for all the objects in the room.
And you also need to be able to compose asynchronous callbacks that make up the entire workflow.

Another problem with this design could be performance, when you issue an Ask to some actor, Akka.NET (and Akka) will spin up a temporary actor that will handle the response message.
Creating actors is fairly fast and cheap, but it is no way near as fast and cheap as just sending plain a message.

a Push based design

A better approach is to make our game push based.
Whenever something or someone enters a room, the room is notified of this and add the incoming object to its own state of contained objects.
At the same time, the room can notify all of the objects it already know about that there is now a new object present in the room.

Pseudo code containedObjects.ForEach(obj => obj.Tell(new AddedContent(incomingObject)))

This will make all the other objects aware of the incoming object, you can also include state like object name.
The incoming object could also be notified by the room where the room tells the incoming object what other objects are already present in the room.

This way, no one needs to ask anyone for information, information is pushed your way when others react to state changes.

The workflow needed to present what other objects are already inside a room would now be something like:

  1. Receive EnteredRoom event, read the PresentObjects property of the event
  2. read the Name property from each object
  3. present the names to the user.

None of the above steps require any sort of asynchronous workflow, it is just logic processing data from a message.

This could greatly simplify the design of some actor, instead of trying to spin up and await for some complext flow of messages, you simply react to incoming messages.

There are of course valid cases where you might want to request information, for example see this blog post about the Work Pulling Pattern

You need to decide from a case to case basis how information flow in your system, but you should be aware that if you are using a lot of Ask, it might just be that you are still stuck in a Request/Response mindset.

Follow Roger Alsing:

Developer, Mentor and Architect. Co-Founder of the Akka.NET actor model framework.

4 Responses

  1. Radek Miček
    | Reply

    This approach without any pushback seems pretty dangerous – eg. when the client receives more messages than it can process.

    • Roger Alsing
      | Reply

      That of course depends on the number of events in flux, if you reverse the process to be based on asking the source, the source could be overwhelmed by events. as always, decide per use case.

      In the example above, each player/object receives one message whenever they enter a room, and then additional messages when others move in or out of the same room.
      That is a nice way to partition your event notifications, only objects nearby receive the events and thus you limit the number of events that are sent.

      But if thousands of players enters the same room, there might be congestion of events, and in a game scenario, it would likely be more correct to throttle and drop messages than to use back pressure. the same goes for stock trading, don’t back pressure, just drop messages on the floor and throttle.. old data is worth nothing in those cases.

  2. Anup Marwadi
    | Reply

    Hello, Interesting Article. I fully agree with you here but I was wondering about an API system where one is building a Shopping Cart. For instance, I have an API call that in turn calls an Actor System to initialize a Cart, check inventory on tickets, add tickets to cart etc. One cannot simply just do a Tell call here right? For instance, I have a call to add 2 tickets to cart, the angular app calls POST carts/{cartId}/tickets and specifies a ticketType and quantity, and the API does an ASK call to the Actor System which will check if tickets are available and then save the tickets if they are and then return back the updated cart. How would a Tell pattern work in such scenarios where the API has to return a result back?

    Looking forward to your response. Thank you!

    • Roger Alsing
      | Reply

      Web API endpoints are inherently request/response based so I see no need to fight that, just do as you do and use Ask to interact with the actorsystem in those.

      If you want to go fully reactive in a web scenario, you can of course use websockets/SignalR in order to have a message channel from your frontend into the backend.
      That would allow you to send a message from the client, and react on that message on the server and send a message to some actor in the backend. and the backend could then send messages back to the client.
      That way you could design a 0-n response pipeline from the server to the client.


Leave a Reply