Road to Akka: creating actors

Akka is an implementation of the Actor model. This means we need to think and program in Actors. Like I said in my previous post, Actors are not like other objects. You should look at Actors as the primitives of concurrent programming. You can’t get an Actor instance, you can’t directly create an Actor instance. A good Actor implementation does not allow other objects to directly change its state. Lets dive deeper into these Actors

Anatomy of an Actor

Consider the following example Actor:

import akka.actor.AbstractActor;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;

public class StringAnalyzer extends AbstractActor {
    
    private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
    
    private boolean keepLatestRequest;
    private int maxStringLength;
    
    public StringAnalyzer(boolean keepLatestRequest, int maxStringLength) {
        this.keepLatestRequest = keepLatestRequest;
        this.maxStringLength = maxStringLength;
    }
    
    public static Props props(boolean keepLatestRequest, int maxStringLength) {
        return Props.create(StringAnalyzer.class, keepLatestRequest, maxStringLength);
    }
    
    private String latestRequest = "";

    @Override
    public Receive createReceive() {
        return receiveBuilder().matchAny(this::talk).build();
    }
    
    private void talk(Object object) {
        System.out.println("i am an actor");
    }

    @Override
    public void postStop() throws Exception {
        log.info("actor has stopped");
    }

    @Override
    public void preStart() throws Exception {
        log.info("actor has started");
    }
}

This is a very simple Actor. It does not do very much actually, it just says “I am an actor” whenever it receives a message, any message. 

Let’s look at the different parts of the code

public class StringAnalyzer extends AbstractActor

The AbstractActor superclass is the template given by the Akka framework. It is an API specifically built for Java 8 to take advantage of lambdas. As the name indicates, its an abstract class with one abstract method: createReceive() which we have to implement. 

 private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);

Akka his its own Logging mechanism where you have to specify the actor system and the current object that logs. Other logging mechanisms can be configured though. Logging happens asynchronously to limit potential performance impact. 

public static Props props(boolean keepLatestRequest, int maxStringLength) {
        return Props.create(StringAnalyzer.class, keepLatestRequest, maxStringLength);
    }

The props() method returns a Props object. This Props object is needed to tell the actor system how to create a specific Actor. It is not necessary to have a method returning a Props object on an Actor, but it is considered good practice to do so. 

@Override
    public Receive createReceive() {
        return receiveBuilder().matchAny(this::talk).build();
    }

The createReceive() method is the brain of the Actor. This method defines how an Actor will react to different incoming messages. This method is mandatory and needs a proper implementation. 

@Override
    public void postStop() throws Exception {
        log.info("actor has stopped");
    }

    @Override
    public void preStart() throws Exception {
        log.info("actor has started");
    }

The postStop() and preStart() are Actor lifecycle hooks. The preStart() method is called during the initialisation of an Actor. To be more specific, when the ActorRef if created. When things go wrong in an Actor, the parent or supervisor of that Actor will automatically restart the broken Actor. During restart, the preStart() method is also invoked. This behaviour can be overridden though. 

The postStop() method is called when an Actor has fully stopped. In this example, it just prints to the console, but it would be a good place to clean up resources here. 

Creating an actor

Before I can go any deeper into the creation of actors, I need to explain the Actor System. The actor system is a big collection of actors, actor ‘mailboxes’ (this is what other objects interact with, remember? Other objects never deal with actors directly) and configuration. I have already mentioned in my previous post that actors really shine in a hierarchy. The actor system is where this hierarchy is made. Before actors can be created, you need a reference to an actor system. This is how to get a reference to an actor system. You can give it a name but it is not mandatory. 

ActorSystem system = ActorSystem.create();
ActorSystem system = ActorSystem.create("name");

An actor system gives us programmers some extra benefits. An actor always has a parent, except of course for the root actor. The actor system creates the root actor for us, together with two syblings. For more information on that, check the akka documentation page here

As you can see, the actor system is quite important. You need an actor system in order to create actors, like this: 

ActorRef creation1 = system.actorOf(ActorCreation1.props(true, 100), "actorCreation1")

In order to create an actor, you need to provide the system with a Props object. It is good practice to have a static Props factory on the actor itself. The above example creates an actor in the actor system which has 2 constructor arguments (“true” and the 100). The other parameter in the actorOf() method is a name you can give to the actor. 

Rules for creating an Actor

These rules aren’t are not official rules. I have been playing around with creating actors in order to learn a bit more about it and I out what works and what does not work. You can try out the code examples for these ‘rules’ here

First, an actor needs a constructor. One might be think that these Props objects are all it takes to get the actor system to create an actor. But that is not true. When you want to create an actor but you have not given it a constructor, you will encounter the following exception: 

Exception in thread “main” java.lang.IllegalArgumentException: no matching constructor found on
* class name  [your arguments]

Secondly, the order of the constructor arguments given to the Props object does matter. Switching those arguments around and hoping on type matching will not work. The following example will throw the same exception.

public ActorCreation3(boolean someBoolean, int someInt) {
        this.someBoolean = someBoolean;
        this.someInt = someInt;
    }
    
    //This Props factory will not work. The order of the arguments is of importance!
    public static Props badProps(int someInt, boolean someBoolean) {
        //so here, the order in which the arguments appear is important. This means
        //that Akka will not scan for the type of the arguments, but just used the 
        //position of the props vararg to decide where to insert the aruments in the constructor
        return Props.create(ActorCreation3.class, someInt, someBoolean);
    }
    
    // Good props. The create method of the Props has the order of the arguments right.
    public static Props props(int someInt, boolean someBoolean) {
        return Props.create(ActorCreation3.class, someBoolean, someInt);
    }

By now we know that we need a constructor. And really, any constructor will do. Even a private constructor will do. Now that I know this, I would even prefer a private constructor. It makes no difference in how the code will run, but I think it really indicates the high level of encapsulation that these actors need. 

A private constructor would prevent any other developers from trying to instantiate your actors. But in all honesty, with Akka it doesn’t really matter. Akka forbids the instantiation of actors without using the actor system. Try for yourself and see what happens. 

Thirdly, the createReceive() method really needs an implementation other then what is automatically created for you or just returning null. An exception will be thrown if you do now return a proper Receive object with a function.