Everything is an Actor

David Khourshid - @davidkpiano

πŸ‘©β€πŸ’»

πŸ§”

πŸ‘©β€πŸ³

β˜•οΈβ”

πŸ“

πŸ’Άβ“

πŸ’Ά

πŸ“„βœ…

β˜•οΈ

β˜•οΈ

πŸ‘©β€πŸ’»

πŸ§”

I would like a coffee...

What would you like?

Β ActorΒ 

I would like a coffee, please.

Β ActorΒ 

πŸ’­

πŸ’¬

πŸ’¬

Here you go. β˜•οΈ

Thanks!

What is an actor?

Actor πŸ‘©β€πŸ’»

Actor πŸ§”

βœ‰οΈ

βœ‰οΈ

someActor.send({
  type: 'greet',
  value: 'Hei!',
  from: self
});

// ...

switch (message.type) {
  case 'greet':
    message.from.send({
      type: 'greet',
      value: '...',
      from: self
    });
  // ...
}

Send

Receive

The Actor Model

A Universal Modular ACTOR Formalism for Artificial Intelligence (1973)

Carl Hewitt, Peter Bishop, Richard Steiger

Alan Kay

Rules of actors

πŸ€– All computation is performed within an actor

πŸ“© Actors can communicate only through messages

πŸ’₯ In response to a message, an actor can:

πŸ“¬ Send messages to other actors

πŸ‘¨β€πŸ‘§β€πŸ‘¦ Create a finite number of child actors

𝑓 (π‘₯)

Behavior

State

🎭 Change its state/behavior

Rules of actors

A

B

βœ‰οΈ

βœ‰οΈ

C

βœ‰οΈ

πŸ€– All computation is performed within an actor

πŸ“© Actors can communicate only through messages

πŸ’₯ In response to a message, an actor can:

πŸ“¬ Send messages to other actors

πŸ‘¨β€πŸ‘§β€πŸ‘¦ Create a finite number of child actors

🎭 Change its state/behavior

Rules of actors

A

βœ‰οΈ

πŸ€– All computation is performed within an actor

πŸ“© Actors can communicate only through messages

πŸ’₯ In response to a message, an actor can:

πŸ“¬ Send messages to other actors

πŸ‘¨β€πŸ‘§β€πŸ‘¦ Create a finite number of child actors

🎭 Change its state/behavior

A β†’ A'

Rules of actors

A

βœ‰οΈ

πŸ€– All computation is performed within an actor

πŸ“© Actors can communicate only through messages

πŸ’₯ In response to a message, an actor can:

πŸ“¬ Send messages to other actors

πŸ‘¨β€πŸ‘§β€πŸ‘¦ Create a finite number of child actors

🎭 Change its state/behavior

B

βœ‰οΈ

Rules of actors

A

βœ‰οΈ

πŸ€– All computation is performed within an actor

πŸ“© Actors can communicate only through messages

πŸ’₯ In response to a message, an actor can:

πŸ“¬ Send messages to other actors

πŸ‘¨β€πŸ‘§β€πŸ‘¦ Create a finite number of child actors

🎭 Change its state/behavior

πŸ†•

πŸ†•

Behavior

idle

making coffee

delivering coffee

β˜•οΈ ordered

β˜•οΈ made

β˜•οΈ delivered

{
  orders: [/* ... */],
  inventory: {
    // ...
  },
  sales: 134.65
}

Finite State (behavior)

Extended State (data)

Actor System

Root (Guardian) Actor

cafe
cafe/manager1
cafe/manager1/employee1

Address

πŸ“«

πŸ“«

πŸ“«

πŸ“«

πŸ“«

πŸ“«

πŸ“«

βœ‰οΈ

state 1

βœ‰οΈ

state 2

βœ‰οΈ

βœ‰οΈ

processing...

state ??

Mailboxes

βœ‰οΈ

βœ‰οΈ

βœ‰οΈ

βœ‰οΈ

Mailboxes

βœ‰οΈ

state 1

βœ‰οΈ

βœ‰οΈ

βœ‰οΈ

Mailboxes

βœ‰οΈ

state 1

βœ‰οΈ

state 2

βœ‰οΈ

βœ‰οΈ

Mailboxes

βœ‰οΈ

state 1

βœ‰οΈ

state 2

βœ‰οΈ

βœ‰οΈ

state 3

Mailboxes

βœ‰οΈ

βœ‰οΈ

state 1

βœ‰οΈ

state 2

βœ‰οΈ

βœ‰οΈ

state 3

Mailboxes

βœ‰οΈ

state 4

Location Transparency

βœ‰οΈβ†’

πŸ’»

πŸ›°

ActorRef

βœ‰οΈ

βœ‰οΈ

Fault Tolerance

βœ‰οΈβ†’

πŸ’»

ActorRef

βœ‰οΈ

❌

πŸ’»

βœ‰οΈ

const popup = window.open(/* ... */);

popup.postMessage(/* ... */);

function receiveMessage(msg) {
  // do something with the message
}

window.addEventListener('message', receiveMessage);
function receiveMessage(msg) {
  // do something with the message

  msg.source.postMessage(/* ... */);
}

window.addEventListener('message', receiveMessage);
const popup = window.open(/* ... */);

popup.postMessage(/* ... */);

function receiveMessage(msg) {
  // do something with the message
}

window.addEventListener('message', receiveMessage);
function receiveMessage(msg) {
  // do something with the message

  msg.source.postMessage(/* ... */);
}

window.addEventListener('message', receiveMessage);
merge(...)
  scan(reducer, initial)

initial

next

pipe(

)

Behavior

Actors

Actor

Parent

  • Any event

SEND

RECEIVE

  • Any event

Actors: Promise

Promise

Parent

  • Resolved value
  • Rejected value
  • AbortController signal

SEND

RECEIVE

or

Actors: Observable

Observable

Parent

  • Multiple values
  • Completion signal
  • Error signal

Nothing *

SEND

RECEIVE

* RxJS has Subjects for this

or

npm install xstate
import { createMachine } from 'xstate';

const machine = createMachine({
  initial: 'idle',
  states: {
    idle: {
      on: { SEARCH: 'searching' }
    },
    searching: {
      on: {
        RESOLVE: 'success',
        REJECT: 'failure',
        SEARCH: 'searching'
      }
    },
    success: {
      on: { SEARCH: 'searching' }
    },
    failure: {
      on: { SEARCH: 'searching' }
    }
  }
});
import { createMachine } from 'xstate';

const machine = createMachine({
  // ...
});

const searchingState = machine
  .transition('idle', 'searching');

searchingState.value;
// => 'searching'
import { createMachine, interpret } from 'xstate';

const machine = createMachine({
  // ...
});

const service = interpret(machine)
  .onTransition(state => console.log(state))
  .start();

service.send('SEARCH');
// State {
//   value: 'searching',
//   ...
// }
import { createMachine, interpret } from 'xstate';
import { from } from 'rxjs';

const machine = createMachine(/* ... */);

const service = interpret(/* ... */).start();

// Services are already subscribable!
const state$ = from(service);

state$.subscribe(/* ... */);
import { createMachine } from 'xstate';

const cafeMachine = createMachine({
  // ...
  states: {
    // ...
    makingCoffee: {
      invoke: {
        id: 'espresso',
        src: espressoMachine,
        onDone: 'deliveringCoffee'
      }
    },
    deliveringCoffee: {
      // ...
    }
  }
});

Invoke a new "actor"

with espressoMachine behavior

"done.invoke.espresso"
import { createMachine } from 'xstate';

const cafeMachine = createMachine({
  // ...
  states: {
    // ...
    makingCoffee: {
      invoke: {
        id: 'espresso',
        src: espressoMachine,
        onDone: 'deliveringCoffee'
      }
    },
    deliveringCoffee: {
      // ...
    }
  }
});

cafeMachine

espressoMachine

done.invoke.espresso

import { createMachine, send } from 'xstate';

const cafeMachine = createMachine({
  // ...
  states: {
    // ...
    makingCoffee: {
      invoke: {
        id: 'espresso',
        src: espressoMachine,
        onDone: 'deliveringCoffee'
      },
      on: {
        CHANGE_ORDER: {
          actions: send((_, event) => event, {
            to: 'espresso'
          })
        }
      }
    },
    // ...
  }
});

cafeMachine

espressoMachine

CHANGE_ORDER

import { createMachine, forwardTo } from 'xstate';

const cafeMachine = createMachine({
  // ...
  states: {
    // ...
    makingCoffee: {
      invoke: {
        id: 'espresso',
        src: espressoMachine,
        onDone: 'deliveringCoffee'
      },
      on: {
        CHANGE_ORDER: {
          actions: forwardTo('espresso')
        }
      }
    },
    // ...
  }
});

cafeMachine

espressoMachine

CHANGE_ORDER

import { createMachine, sendParent } from 'xstate';

const cafeMachine = createMachine({
  // ...
});

const espressoMachine = createMachine({
  // ...
  states: {
    // ...
    broken: {
      entry: sendParent('MACHINE_BROKEN')
    },
    // ...
  }
});

cafeMachine

espressoMachine

MACHINE_BROKEN

import { createMachine, assign, spawn } from 'xstate';

const createOrder = (details) => {/* ... */}

const cafeMachine = createMachine({
  context: {
    orders: []
  },
  // ...
  on: {
    PLACE_ORDER: {
      actions: assign({
        orders: (context, event) => {
          const order = spawn(
            createOrder(event.details),
            event.orderId);
          
          return context
            .orders.concat(order);
        }
      })
    }
  }
});

order 1

order n

cafeMachine

. . .

import { createMachine, send } from 'xstate';

const cafeMachine = createMachine({
  // ...
  on: {
    CANCEL: {
      actions: send('CANCEL', {
        to: (_, event) => event.orderId
      })
    }
  }
});

order 1

order n

cafeMachine

. . .

CANCEL

import { createMachine } from 'xstate';
import { invokeObservable } from 'xstate/invoke';

function createOrder(details) {
  return /* ... */
}

const cafeMachine = createMachine({
  // ...

  invoke: {
    src: invokeObservable(() => /* ... */)
  },
  
  // ...

  on: {
    ORDER: {
      actions: assign({
        orders: (context, event, { spawn }) => {
          const order = spawn.from(
            createOrder(event.details), event.id);
          
          return context.orders
            .concat(order);
        }
      })
    }
  }
});

5.0

(tentative API)

Returns an actor creator

import { createBehavior, createSystem } from 'xactor';


const counter = createBehavior((state, message) => {
  if (message.type === 'add') {
    return {
      count: state.count + 1
    }
  }
  
  return state;
}, { count: 0 });


const counterSystem = createSystem(counter, 'myCounter');


counterSystem.send({
  type: 'add'
});
import { createBehavior, createSystem } from 'xactor';


const createTodo = (title) => createBehavior((state, message) => {
  // ...
}, { title })


const todos = createBehavior((state, message, ctx) => {
  switch (message.type) {
    case 'todo.create':
      const newTodo = ctx.spawn(createTodo(message.title), `todo-${state.todos.length}`);
      
      return {
        ...state,
        todos: [
          ...state.todos,
          newTodo
        ]
      };
  }
}, { todos: [] });


const todoSystem = createSystem(todos, 'myCounter');


todoSystem.send({
  type: 'todo.create',
  title: 'Learn the actor model!'
});

Thank you ReactiveConf!

David Khourshid - @davidkpiano

Everything is an Actor

By David Khourshid

Everything is an Actor

  • 3,092