Mastering the Receiver Pattern in Akka Typed Actors

Snippet of programming code in IDE
Published on

Mastering the Receiver Pattern in Akka Typed Actors

Akka Typed is a powerful toolkit for building concurrent and distributed systems in Java and Scala. It brings an innovative way to manage state and behavior using Actors, encapsulating the sender-receiver communication model efficiently. In this blog post, we will delve deep into the Receiver Pattern within Akka Typed, exploring its importance, functionality, and how you can implement it in Java.

What is the Receiver Pattern?

The Receiver Pattern in Akka Typed actors is a design strategy that promotes immutability and messages processing through predefined state transitions. By using this pattern, actors can handle incoming messages and maintain state in a clear and organized manner. The concept is built around the idea that an actor can change its behavior dynamically through messages, enabling great flexibility in interactions.

Why Use the Receiver Pattern?

  1. Encapsulation of State: The Receiver Pattern allows actors to manage their state privately, thus preventing accidental modifications from outside sources.
  2. Clear Design: It establishes a clear structure for handling various events. Each state transition can be encapsulated in a dedicated behavior.
  3. Dynamic Behavior: Actors can define new behaviors based on incoming messages effectively, making the system adaptive.

Setting Up Akka Typed in Your Java Project

Before we dive into the Receiver Pattern, let’s ensure you have Akka Typed set up in your project. You can use Gradle or Maven. Here is how you can do it with Maven:

<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-actor-typed_2.13</artifactId>
    <version>2.6.19</version> <!-- Update to the latest version -->
</dependency>

Implementing the Receiver Pattern

Let’s create a simple actor that models a light switch. The light switch can be in either an "on" or "off" state. Depending on the received messages, it switches between these states.

Step 1: Create the Messages

We first need to define the messages our actor will handle.

public sealed interface LightMessage permits TurnOn, TurnOff {
}

public final class TurnOn implements LightMessage {
}

public final class TurnOff implements LightMessage {
}

Here, the LightMessage interface defines the command structure. It uses Java’s sealed interfaces to restrict subclasses, ensuring all possible messages are defined.

Step 2: Define the Actor Behavior

The next step involves creating the actor’s behavior. We will define two behaviors: one for when the light is off and another for when it's on.

import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.AbstractBehavior;
import akka.actor.typed.javadsl.Behaviors;

public class LightSwitch extends AbstractBehavior<LightMessage> {

    private LightSwitch(ActorContext<LightMessage> context) {
        super(context);
    }

    public static Behavior<LightMessage> create() {
        return Behaviors.setup(LightSwitch::new);
    }

    @Override
    public Receive<LightMessage> createReceive() {
        return newReceiveBuilder()
                .onMessage(TurnOn.class, this::onTurnOn)
                .onMessage(TurnOff.class, this::onTurnOff)
                .build();
    }
    
    private Behavior<LightMessage> onTurnOn(TurnOn message) {
        System.out.println("Light is ON");
        return Behaviors.same(); // The behavior remains the same
    }

    private Behavior<LightMessage> onTurnOff(TurnOff message) {
        System.out.println("Light is OFF");
        return Behaviors.same(); // The behavior remains the same
    }
}

Explanation of the Code

  1. Abstract Behavior: We extend AbstractBehavior<LightMessage> which will manage message processing for our actor.
  2. Static Create Method: This sets up the actor instance.
  3. Creating Receive: The createReceive method specifies how the actor should respond to incoming messages.
  4. Message Handlers: The onTurnOn and onTurnOff methods handle respective messages and print the light status.

Step 3: Running the Actor

To interact with our newly created actor, you need an actor system. Below is how you can create the actor and send messages.

import akka.actor.typed.ActorRef;
import akka.actor.typed.ActorSystem;

public class Main {
    public static void main(String[] args) {
        ActorSystem<LightMessage> actorSystem = ActorSystem.create(LightSwitch.create(), "LightSystem");

        ActorRef<LightMessage> lightSwitchRef = actorSystem;
        lightSwitchRef.tell(new TurnOn());
        lightSwitchRef.tell(new TurnOff());

        // Shutdown the actor system gracefully
        actorSystem.terminate();
    }
}

Explanation of the Code

  1. Actor System: We create an ActorSystem that will manage lifecycle and supervision for our actor.
  2. Sending Messages: We use the tell method to send TurnOn and TurnOff messages to the lightSwitch.
  3. Terminate System: Finally, we ensure a clean shutdown of the actor system.

The Last Word

The Receiver Pattern in Akka Typed Actors is a valuable design practice that streamlines state management and makes your systems more robust. The pattern allows actors to maintain internal logic neatly while promoting immutability and clear message handling strategies.

By adapting the Receiver Pattern in your projects, you gain increased flexibility, safety, and clarity in your actor-based solutions. For more in-depth information, here are some useful resources:

Feel free to implement and experiment with the Receiver Pattern in your projects. Happy coding!