How the Actor Model works by example

The Actor Model is a style of software architecture in which the basic computational unit is called an actor.

An actor is similar to a class in object-oriented programming, in that it encapsulates behavior and state. In fact, many Actor Model frameworks represent actors as classes.

Actors vs. classes

What differentiates an actor from a class are the following set of principles:

  • Actors run concurrently.
  • Actors send messages asynchronously.
  • Actors do not share state.
  • The location of an actor is transparent.

Scalability through concurrency

The Actor Model is intended for very large systems that support a great many users simultaneously. As such, actors are independent and asynchronous. A typical use case well-suited for the Actor Model is a shopping cart in an e-commerce application.

At scale, such an application quite conceivably can have thousands or millions of users, each with their own shopping cart. In the Actor Model, each shopping cart is represented as a distinct actor among many that run simultaneously.

In order to work efficiently the shopping carts must run concurrently and not create blockage or race conditions. Thus, each actor is an asynchronous actor that does work when it can, as it can with no direct dependency on the state or activities of other actors.

Loose coupling through asynchronous messaging

Unlike class-to-class interactions — in which one class can create an instance of another class and then call public methods and properties on that other class directly — under the Actor model all communication between actors is conducted asynchronously via messages.

There is no other means of communication.

Typically, some type of message broker technology manages messaging activity between actors. Some frameworks such as Akka abstract the message broker via configuration thus making it opaque to underlying actors.

State isolation

Under the Actor Model, the state of each actor is separate from all other actors in the system. This makes sense; actors are asynchronous by nature.

It’s quite conceivable to have a system in which many continuously changing actors operate concurrently, all the time. Thus, it’s difficult if not impossible for one actor to share the state of another actor at any given moment. If things change too quickly, race conditions can occur.

A better approach is to make each actor responsible for its own state, and also to communicate information that might be relevant to other actors within the payload of an asynchronous message.

Location transparency

In the Actor Model, actors can be distributed among any number of host computers within a given geography.

For example, Actor A can be hosted on a computer running in the western US while Actor B runs on a computer in northern India.

Understanding messaging

As mentioned above, all communication between actors is conducted via asynchronous messaging. There are three modes of message interaction:

  1. Fire and forget it.
  2. Request and response mode.
  3. Message forwarding.

Fire and forget occurs when Actor A sends a message to Actor B, then Actor A moves forward with its execution expecting no response from Actor B.

The response mode is when Actor A sends a message to Actor B but does expect a reply from Actor.

Message forwarding is when Actor A sends a message to Actor B along with instructions to forward a response message to Actor C.

Figure 1 below illustrates the three modes of communication.

Figure 1: The 3 modes of asynchronous communication among actors in the Actor Model

In terms of asynchronous communication, at the conceptual level, message order is not guaranteed. Also, the speed of message delivery between actors is arbitrary.

Actor Model Frameworks

The following table lists a sampling of formal frameworks for the Actor Model.

Framework Description
Akka A popular framework with SDKs for in Java, Scala and .NET.
Microsoft Orleans An Actor Model framework created by Microsoft, also referred to as Distributed .NET. Microsoft Orleans is well-suited for cloud services such as Azure running under Kubernetes.
Erlang OTP Erlang/OTP (Open Telecom Platform) is a collection of libraries, frameworks and tools built on top of the Erlang programming language for building large-scale systems using the Actor Model.
Proto.Actor An Actor Model framework that supports the .NET, Go and Kotlin programming languages.
Pykka An implementation of the Actor Model in Python.
Celluloid Celluloid is a framework that provides an asynchronous, multi-threaded environment to create systems based on the Actor Model in the Ruby programming language.
CAF The C++ Actor Framework (CAF) is a framework to implement the Actor Model in C++.

Implementing an Actor Model

As mentioned at the start, this article includes an application with source code that demonstrates how to implement an commerce shopping cart using the Actor Model.

The demonstration application is written in Java using the Akka framework for the Actor Model.

Actor model demonstration

Figure 2: The basic structure of the Jill’s Juice shopping cart demonstration application.

It takes some knowledge of how Akka works to get a nuanced understanding of this shopping cart application. What’s important to understand at a high level, however, is that all communication with the shopping cart and its subordinate actors is conducted via messages.

Let’s look at how this communication works.

An actor called ShoppingCartActor represents the shopping cart. The client that sends messages into the ShoppingCartActor is a controller actor called App.

The App actor sends a fire-and-forget message, AddItems, that describes a set of items to add to the ShoppingCartActor. Another fire-and-forget message named CheckoutInfo describes the checkout information required to execute checkout behavior within the ShoppingCartActor. Sending a CheckoutInfo to the ShoppingCartActor instigates the checkout process (see Figure 2, callout 1).

The checkout process within the ShoppingCartActor sends a response message to the PaymentActor which processes payment and returns a response (see Figure 2, callout 2a). After the PaymentActor processes payment it sends a fire-and-forget PaymentReceipt message to the CustomerActor (See Figure 2, callout 3a).

After payment is made and acknowledged by a response from the PaymentActor, the ShoppingCartActor sends a ShippingInfo message to the ShippingActor (see Figure 2, call out 2b). The ShippingActor ships the items described in the ShippingInfo message, and then responds to the ShoppingCartActor to let it know that shipment has executed. Also, the ShippingActor sends a fire-and-forget ShippingReceipt message to the CustomerActor to inform the customer that the items have shipped (see Figure 2, callout 3b).

When to use the Actor Model

The actor model is particularly useful for the following types of applications:

  1. Complex workflows.
  2. Streaming apps.
  3. Concurrent applications.
  4. Highly available systems.

Workflow processes

The actor model is useful for applications that manage workflow processes, particularly complex ones. Thus, for example, the shopping cart example described above and demonstrated in the project dedicated to this article on GitHub.

Also, the Actor Model applies well to manufacturing processes. Any process that has a series of steps and is intended to run at scale will benefit from using the Actor Model.

Data streaming

The Actor Model can be applied to any application that emits and consumes a continuous stream of data.

For example, television streaming services such as Netflix and Hulu are well-suited to the Actor Model. Most modern streaming services emit a message every time a user selects shows, views a show, pauses a show or traverses forward and back through a show.

In turn, each of these messages is consumed by another process or service.

Emitting and consuming messages is foundational to the Actor Model. Thus, using the Actor Model to implement a television streaming service, as well as with other types of similar applications, is a natural fit.

Multi-user concurrency

Architecturally, the Actor Model is intended to support multi-user concurrency. The mechanisms by which Actor Model frameworks support concurrency is supported vary, but overall these frameworks work to ensure that each actor in the Actor Model implementation behaves independently and uses system resources in an efficient, non-blocking manner.

The Actor Model fosters inherent support for concurrency which makes it attractive for applications that support fast-paced interactions executed by a large number of users at the same time. Prime examples of this include large-scale gaming and gambling applications.

System requiring high uptime and high availability

Most Actor Model frameworks ship with fail-safety and redundancy features. This means that programmers can configure the given Actor Model framework to react to and rectify actor failures as they happen.

Sometimes this takes the form of programmable retry behavior.

At other times, the framework might reroute messages away from a failing actor to an instance of one that is functioning properly.

The ability to absorb system failures means that an Actor Model framework can guarantee five nines availability, which is a term used to describe a very high level of system availability (99.999% uptime).

When not to use the Actor Model

The Actor Model is not a one-size-fits-all solution. There are situations when the Actor Model is an inappropriate choice. The following describes two such situations.

Non-concurrent systems

Not every application must support concurrency as defined by the Actor Model. For example, applications that only perform one function, such as complex calculations, are best implemented as a discreet, independent service or application.

An actor in an application that implements the Actor Model might use a service such as a complex calculator, but the complex calculator can achieve scale using techniques other than Actor Model.

Performance-critical applications

As mentioned previously, under the Actor Model, delivery time of messages is arbitrary. Some messages might reach a target quickly, while others take extra time. Thus, applications such as stock trading applications that require execution rates measured in milliseconds might find the Actor Model wanting.

The Actor Model is intended to support millions of users concurrently, but there is no guarantee that the Actor Model can support millions of users quickly. There are too many things that can go wrong.

When it comes to high-volume concurrent systems, there is always a tradeoff between volume and speed.

Drawbacks and risks with the Actor Model

The Actor Model has many benefits, but one must use it with caution.

Central controller

One of the biggest risks with the Actor Model is that one can create an architecture with too many actors in play that are not managed by some sort of central controller.

This entity knows the state of each actor that works within the system, and monitors those subordinate actors to ensure that they operate properly.

Also, the central controller provides a way to describe the behavior of the system overall, and can serve as a reference by which to understand the composition of the system.

In an implementation of an Actor Model with a large number of actors that execute in a sequential and independent manner without the aid of a central controller, it becomes very difficult to keep track of the system’s overall behavior and composition.

Testing and debugging

Another drawback of an Actor Model implementation with too many actors is that testing and debugging become arduous undertakings.

Actor behavior is, by definition, asynchronous and independent. This makes debugging hard.

Often it’s not simply a matter of putting some breakpoints in code and running the debugger. More is required, particularly to understand errors due to race conditions. Thus, the more actors a system has, the more difficult it becomes to troubleshoot.

Determining the optimal number of actors in a system is an art as much as a science. Developers should be aware of the risks at hand and limit the actors in a system to a number that is absolutely essential.

Putting it all together

The Actor Model has many benefits. The pattern is well-suited for applications that must support concurrency at a very large scale, on the order of hundreds of thousands if not millions of users.

Also, the Actor Model put separation of concerns at the center of the development process, both in terms of architecture design as well actually building the software. This means that developers maintain a high degree of independence as they implement the Actor Model.

Still, to implement the Actor Model requires commitment from both developers and the business.

Once a company adopts the Actor Model, it becomes a way of life. This architecture cannot simply be undone. Companies should diligently examine the business’s requirements to ensure the Actor Model really meets the needs at hand.

Get the source code for the demonstration application

This article has an accompanying demonstration application with source code hosted on GitHub. The application was created specifically for the article and demonstrates the principles described herein.

The demonstration application is named “Jill’s Juice.” Jill’s Juice is a fictitious e-commerce company that sells a variety of gourmet juices. This GitHub project shows how to implement the Actor Model as a simple shopping cart. The implementation uses the Akka framework under Java.