diff --git a/content/posts/2023-5-30-what-is-the-actor-model-and-when-should-i-use-it.mdx b/content/posts/2023-5-30-what-is-the-actor-model-and-when-should-i-use-it.mdx new file mode 100644 index 0000000..40ce44e --- /dev/null +++ b/content/posts/2023-5-30-what-is-the-actor-model-and-when-should-i-use-it.mdx @@ -0,0 +1,98 @@ +--- +title: What is the Actor Model and When Should I Use It? +description: "Defines the Actor Model, its use cases, discusses using it with statecharts" +tags: + - actor model + - modeling + - statechart + - tutorials + - state machine + - typescript + - blog +author: Gavin Bauman +publishedAt: 2023-5-30 +--- + +At Stately, the [Actor Model](https://en.wikipedia.org/wiki/Actor_model) is one of our favorite programming paradigms, and we think it’s for good reason! The actor model allows developers to build reliable message-based systems by using _actors_ to communicate. This works extremely well with state machines and statecharts, which can also be modeled as actors and can communicate much in the same ways. Read on to learn what the actor model is, the problems it seeks to solve, and how you can use it in your projects to communicate reliably across different entities. + +## What is the actor model? + +The actor model has been around for quite a while, dating back to the 1970’s. That it’s used with frameworks like [Akka](https://akka.io/) and built natively into languages like Erlang(https://www.erlang.org/) are testaments to its utility. When researching the actor model, it’s very common to see the phrase “everything is an actor”, as it is a core tenant in the actor model philosophy. This simply means that in a given system, the _actor_ is the core unit of execution. Every action that occurs in the system is driven by an actor. An actor can communicate with other actors with the use of messaging, and they can also interact with external systems. Specifically, an actor can perform the following basic tasks: + +- Actors can spawn other actors +- Actors can send messages to other actors +- Actors can manage their own internal state + +It may sound simple, but this programming model allows for the development of highly scalable and concurrent systems. There are constraints though, the most important of which is that an actor _cannot_ modify the internal state of another actor directly. This can be done implicitly with messaging (i.e an actor updating its state in response to a message) but _never directly_. + +## When should I use it? + +The actor model is extremely useful for divvying work that can be processed in parallel. It’s excellent for “fan-out/fan-in” scenarios where several functions need to be run at once and have their results combined before final processing. It also works well with building pub/sub systems, where several actors can be “workers”, waiting for messages from the “publisher”. Our last example, but certainly not the least, is in the case of systems that need to manage several similar entities, like a multiplayer game where every player is represented as an actor. A good rule of thumb is to at least consider the actor model whenever distribution and concurrency are core requirements. + +## When not to use it + +As with any pattern, it’s just as important to understand its weaknesses as it is to understand its strengths. Typically you may not want to use the actor pattern when order really matters. Order is usually not promised in the actor pattern, and if one of the actors fails, you’ll have to deal with the concern of rolling back events. The actor model is often unnecessary when dealing with synchronous problems as well, and can add unnecessary overhead. Additionally, error handling can be tricky with actors. Erlang popularized the “let it crash” philosophy, but given the problem, it may not always be the most reasonable answer. + +## How does Stately work with the actor model? + +With XState, we expose the ability to create instances of machines as actors! Looking at the example below, we can see that after defining our machine and its attributes, we only need to use `interpret()` to instantiate an actor (called `toggleActor`) and send it messages. + +```typescript +import { createMachine, interpret } from "xstate"; + +// State machine definition +const toggleMachine = createMachine({ + id: "toggle", + initial: "inactive", + states: { + inactive: { on: { TOGGLE: "active" } }, + active: { on: { TOGGLE: "inactive" } }, + }, +}); + +// Machine instance with internal state +const toggleActor = interpret(toggleMachine); +toggleActor.subscribe((state) => console.log(state.value)); +toggleActor.start(); +// => logs 'inactive' + +toggleActor.send({ type: "TOGGLE" }); +// => logs 'active' + +toggleActor.send({ type: "TOGGLE" }); +// => logs 'inactive' +``` + +This actor has its own state and context, and it can be updated whenever it receives events. Of course, in order for the internal state of an actor to be updated, the event must be a legal transition defined with the machine. + +### Spawning Actors + +XState can also be used to spawn other actors and communicate with each other: + +```typescript +import { createMachine, spawn } from "xstate"; +import { todoMachine } from "./todoMachine"; + +const todosMachine = createMachine({ + // ... + on: { + "NEW_TODO.ADD": { + actions: assign({ + todos: (context, event) => [ + ...context.todos, + { + todo: event.todo, + // add a new todoMachine actor with a unique name + ref: spawn(todoMachine, `todo-${event.id}`, { sync: true }), + }, + ], + }), + }, + // ... + }, +}); +``` + +With the use of `spawn()` and `assign()`, we create a new actor instance when provided the machine and a unique identifier. By their nature, actions are ["fire and forget" effects](https://xstate.js.org/docs/guides/actions.html#api), meaning they are actions that are executed with no expectation of receiving events back to the actor. This makes sense for creating a new actor, but we may still want the parent actor to have a reference to its child, so we save that in its context using `assign()`. `spawn()` is the function called that actually creates the new actor. `spawn()` also sports another useful feature in that it accepts an `options` object with an optional `sync` property. Setting this to `true` allows the parent actor to subscribe to the child’s current state. The parent can access this state easily by calling `getSnapshot()` on the reference to the child. + +For more detailed examples around working with actors in XState, like callback or promised-based actor spawning, sending updates, and communicating between actors, [check out the guides in the XState documentation](https://xstate.js.org/docs/guides/actors.html).