We’ve been working for several years now on the implementation of an actor model as the one introduced by the Polymer team in this talk
We think that an actor model is not only great to multi-thread in web workers, adapt the interface to the bandwidth context… but simply to keep a very clean, understandable and scalable code… Everything is an actor, messages are “typed” thanks to Typescript, calculations are siloed and pure inside actors, side-effects are taken care thanks to special actors like components so this framework is UI agnostic;
Here is what an actor can do in the actor model (expert discussion here)
- send a finite number of messages to other actors;
- create a finite number of new actors;
- designate the behavior to be used for the next message it receives.
We like to think actors as state machine such as the ones described in Xstate
We tried to simplify the actor model by defining the following specific actors
- THE channel (simplification of a broadcaster actor that would have three messages : createActor(actorName, actor) to create the other actors, and dispatch(messageName, message) to dispatch the messages it receives to all the other actors it created and a getState(actorName) that would emit a message to get a stores’ state, receive it and send it back to the asker. Its states holds the reference to all the other actors ) : for simplicity’s sake we create the Channel as a Subject singleton to which the other actors subscribe and in which the actors messages pours in. The messages don’t refer to one particular actors but each actor can decide whether it reacts to this message or not.
- sources (run once directly when they are created and emit one or more messages in the channel but don’t listen to any messages)
- stores (simplification of actors that hold a state that can change at any given message through reducers and that can send back their state through getState message : for simplicity’s sake we don’t send the state through the channel and the storeStates is directly accessible through a channel getStore
- components (a special kind of actor to which the transition is a render method, that can also dispatch events such as clicks !..)
The rest are regular actors. For instance the epics described in redux-observable are actors in our model.
Let’s take this typeahead real world example borrowed from Floyd May great article.
Here are various solutions to solve this problem
Here is our way to solve it
an input component that sends “keystroke” in the channel
a fetch actor that switchmap on “fetch” messages and send “fetched” messages
a typeahead actor that listens to “keystroke” and “fetched” messages, send “fetch” and “typeahead_results” messages (this actor is the state machine, includes the debounce, decides when to fetch the data etc…). It can also send a “loading” message to dispatch the fact that new results are about to arrive.
a typeahead_results component that listens to “typeahead_results” messages, render and enable to select them by sending a “select” message. An interesting feature would be for the output component to be able to compare the next result list to the previous and change the list smoothly. Another feature could be to show a loader when the loading
a selected component that listens to “selected” and “remove_selected” messages and enable to remove them by sending a “remove_selected” (to it self !). This component holds the state of the selected items.
The fetch_data can become a fetch actor and the data is passed in the payload