The Wind.js Actor Model

Jul 22, 2018 02:00 · 1043 words · 5 minute read software design actor model serviceless design

Edit: I’ve developed a really simple POC on how to use wind.js with express to expose a ToDo List. You can see the code here.

I’ve been always fascinated on how the actor model works because from a software perspective, it’s like modeling the real world. You build an ecosystem of objects that communicate between them actively, handle their own life-time and data.

The Actor model became popular with Erlang, that made relatively easy to build actors and use this design pattern as the main building block of software. Erlang is a functional language, and the idea of defining actors was to represent mutable thread-safe structures in a programming language without the drawbacks of current object-oriented languages.

After that, other projects have been released. Some popular projects like Akka are highly influenced by Erlang and its actor model, other not that much (like Service Fabric Reliable Actors).

However, the Actor model has always been used to simplify parallel computing on backend services when it has a lot of other benefits outside thread-safety. That’s why I wanted to bring the actor model to frontend applications. And I started wind.js.

Benefits of the Actor Model

Reliability

First of all, and for me it’s the main point of the actor model, is reliability. Reliability comes in two flavours in the actor model.

A single actor is reliable because all business logic scoped to that actor is transactional. That means that, if an actor crashes during the process, the state of the actor is not changed. For example, if we have this actor:

class CountActor extends system.Actor {
    constructor() {
        this.count = 0;
    }

    onReceive() {
        this.count += 1;
        throw "forced failure";
    }
}

let counter = new CountActor();
system.tell(counter.id, "count");

The count of the actor will never increase, as it’s throwing an exception even if we are increasing the counter.

Also, an actor system is reliable, as a failing actor wouldn’t bring down all other actors. The actor runtime is totally independent of other actors (I will write a post about it, because changes the SOLID perspective) implementing the Active Object pattern.

Performance

Actors are non-blocking and thread safe, so it means that the performance is really good in multi-threading environments. In a browser, actors are performant because they are pulling theirselves from their own event-loop, not blocking other actors. Actors will be more useful when Web Workers arrive to all browsers.

Transparency

Actors are transparent and easy to debug. All actors and messages are saved on each successful transaction on a time machine, so you can investigate the current state and how messages lead to it further. Also, messages always come with information about the sender, so you can track other actors that participated on the business logic.

Actors work independently of the communcation mechanism

Actors can receive messages from other actors directly (through tell & ask patterns) or publish and receive messages from an event bus that is unique in the actor system. Those messages will be treated as any other message.

Extensibility

Actors are objects, and so the actor system. Actors can be composed, inherited and improved through common patterns like proxys. In wind.js this is increased through supervisors and materializers.

How To Start With Wind.js

The easiest way to start with wind.js is to create a new npm project and install wind.js:

PS> cd Projects
PS> mkdir my-actor-project
PS> cd my-actor-project
PS> npm init # defaults will work
PS> npm install wind.js --save

This project will run in node, as it will be easier to try and play without a browser. When ready, create a file named index.js with the following content (that will be explained later):

let ActorSystem = require("wind.js");

let system = new ActorSystem();
system.start();

class Ping extends system.Actor {
    onReceive({message}) {
        console.log(message.toUpperCase());
    }
}

let myPinger = new Ping();
system.tell(myPinger.id, "ping");

system.stop();

Now let’s dive into the code to understand each component in the game.

let ActorSystem = require("wind.js");

let system = new ActorSystem();
system.start();

Actors are created under an actor system (which is the class responsible of finding and spawning actors). The system will handle the access to CPU ticks through a scheduler, that will delegate ticks to actors, and actors will decide if they want accept them or not.

When the system is instantiated, we start it, so the scheduler runs. The application will remain active until the actor system is shutdown.

class Ping extends system.Actor {
    onReceive({message}) {
        console.log(message.toUpperCase());
    }
}

This snippet creates an actor class. All actor classes should inherit from the system.Actor dynamic class, so all actors will be scoped to that actor system. All actors must also implement a method call onReceive, that will receive as a parameter a plain JSON object with the context of the communication. Usually the most useful field of the context is the message and others can be ignored.

let myPinger = new Ping();
system.tell(myPinger.id, "ping"); // => Outputs: PING

Now we instantiate an actor of the actor class Ping and send it a message. There are two basic ways of communicate with actors:

  • Through the actor system (as in the sample) where the system puts a message in the actor mailbox (using the tell method).
  • Through another actor, where an actor puts the message in the receiver mailbox, to be scheduled later.

Technically it’s possible to call onReceive directly on an actor (as this is JavaScript, we can not forbid it) but the only managed way to communicate and get benefit of the actor is through the tell method of the system or the actor.

There are other communcation patterns, but they will get covered in other posts.

system.stop();

After we finished processing everything, we can stop the actor system gracefully. The stop method will make the actor system enter in a shutdown state, and what happens is that:

  • Messages that are already in mailboxes will be processed.
  • New generated messages will not be sent to other actors mailboxes.

When all mailboxes are empty, the system will shutdown, also stopping the scheduler (and freeing the system).

And that’s all for this post, if you are interested you can check the showcase folder ,the architectural documentation and jsdoc.

Also feel free to open issues asking for features, doing Pull Requests, or contacting us for more info 😊