Skip to content
A collection of common actor patterns for the Riker framework
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
config
src
tests
.gitignore
Cargo.toml
LICENSE
README.md

README.md

Riker Patterns

Overview

The patterns crate provides a collection of common patterns used in actor systems.

Currently this only includes the Ask pattern and a behavior change pattern that provides a transform! macro.

The intention is to add many patterns, some of which are well documented in popular actor programming books, such as Reactive Messaging Patterns with the Actor Model.

Patterns:

Ask

The Ask pattern allows values to be sent by actors to outside of the actor system. The value is delivered as a Future.

Let's look at how this works: Cargo.toml:

[dependencies]
riker = "0.1.8"
riker-patterns = "0.1.8"

main.rs:

extern crate riker_patterns;

use riker_patterns::ask;

struct MyActor;

impl Actor for MyActor {
    type Msg = u32;

    fn receive(&mut self,
                ctx: &Context<Self::Msg>,
                msg: Self::Msg,
                sender: ActorRef<Self::Msg>) {

        // sender is the Ask, waiting to a message to be sent back to it
        sender.try_tell(msg * 2, Some(ctx.myself()));
    }
}

fn main() {
    let model: DefaultModel<u32> = DefaultModel::new();
    let sys = ActorSystem::new(&model).unwrap();

    let props = MyActor::props();
    let my_actor = sys.actor_of(props, "my-actor");

    // ask returns a future that automatically is driven
    // to completion by the system.
    let res = ask(&sys, &my_actor, 100);

    // the result future can be passed to a library or fuction that
    // expects a future, or it can be extracted locally using `block_on`.

    let res = block_on(res).unwrap();
    println!("The result value is: {}", res);
}

In the background Ask sets up a temporary intermediate actor that lives for the lifetime of the ask. Other actors see this temporary actor as the sender and can send a message back to it. When the temporary ask actor receives a message it fulfills the outstanding future and performs a stop on itself to cleanup.

Ask is particularly useful when you have part of an application that runs outside of the actor system, or in another actor system, such as a web server (e.g. Hyper) serving API requests. The resulting future can then be chained as part of the future stack.

Transform

Transform makes changing actor behavior based on its current state easier to reason about. Since actors maintain state, and indeed is a primary concern, being able to handle messages differently based on that state is important. The Transform pattern separates message handling by dedicating a receive function per state. This saves excessive matching to handle several possible states, i.e. handling behavior is pre-empted at the time of state change instead of on each message receive.

!!! info If you're familair with Akka on the JVM, transform resembles become.

Example:

#[macro_use]
extern crate riker_patterns;

use riker_patterns::ask;

impl ShoppingCart {
    // created state
    fn created(&mut self,
                ctx: &Context<MyMsg>,
                msg: MyMsg,
                sender: Option<ActorRef<MyMsg>>) {

        match msg {
            MyMsg::AddItem(item) => {
                // add item to cart
                ...
            }
            MyMsg::Cancel => {
                // future messages will be handled by `fn cancelled`
                transform!(self, UserActor::cancelled);
            }
            MyMsg::Checkout => {
                // future messages will be handled by `fn checkout`
                transform!(self, UserActor::checkout);
            }
        }
    }

    // cancelled state
    fn cancelled(&mut self,
                ctx: &Context<MyMsg>,
                msg: MyMsg,
                sender: Option<ActorRef<MyMsg>>) {

        match msg {
            MyMsg::AddItem(item) => {
                // can't add item to cancelled cart
                ...
            }
            MyMsg::Cancel => {
                // can't cancel an already cancelled cart
                ...
            }
            MyMsg::Checkout => {
                // can't checkout out a canclled cart
                ...
            }
        }
    }

    // checkout state
    fn checkout(&mut self,
                ctx: &Context<MyMsg>,
                msg: MyMsg,
                sender: Option<ActorRef<MyMsg>>) {

        match msg {
            MyMsg::AddItem(item) => {
                // can't add item to cart during checkout
                ...
            }
            MyMsg::Cancel => {
                // future messages will be handled by `fn cancelled`
                transform!(self, UserActor::cancelled);
            }
            MyMsg::Checkout => {
                // cart already in checkout process
                ...
            }
        }
    }
}

impl Actor for ShoppingCart {
    type Msg = MyMsg;

    fn receive(&mut self,
                ctx: &Context<Self::Msg>,
                msg: Self::Msg,
                sender: Option<ActorRef<Self::Msg>>) {

        // just call the currently set transform function
        (self.rec)(self, ctx, msg, sender)
    }
}

The transform! macro expects the field name of the current receive function on self to be named rec. It's easy to use a different name and either use your own macro, or just set the fuction using standard code. The advantage of transform! is that it is easy to read and identify when transformation is happening since it is distinct from standard code.

You can’t perform that action at this time.