Riker Actor Framework

repository·master·Indexed 22 days ago

https://github.com/riker-rs/riker

An Actor Framework for Rust designed to build fast, highly concurrent, and resilient applications. Riker provides an actor-based execution runtime, supervision for failure isolation and recovery, and a modular system scalable from IoT/robotics hardware to microservice architectures. Key features include concurrency built on futures::execution::ThreadPool, Pub/Sub messaging, CQRS support, and a procedural macro system via riker-macros to simplify actor definitions.

Tokens
10.7K
Snippets
37
Records
55
Agent score
77%

What's inside riker

  1. What is Riker?

    master

    Riker is a framework for building modern, concurrent, and resilient systems using the Actor Model in Rust. It provides an actor-based execution runtime, actor supervision for failure isolation and recovery, and a modular system designed for scalability.

    Key features include:

    • Concurrency built on futures::execution::ThreadPool
    • Publish/Subscribe messaging via actor channels
    • Message scheduling
    • Command Query Responsibility Segregation (CQRS)
    • Configurable, non-blocking logging
    • Support for running futures easily
  2. How to use Riker in a Rust project

    master

    To use Riker, add it to your Cargo.toml dependencies. You can then define an actor by implementing the Actor trait, specifying its message type, and defining how it handles received messages via the recv method. To run the system, initialize an ActorSystem, spawn your actor using actor_of, and communicate with it using tell.

    Note: Riker is currently built using the latest Rust Nightly version.

    [dependencies]
    riker = "0.4.1"
    use std::time::Duration;
    use riker::actors::*;
    
    #[derive(Default)]
    struct MyActor;
    
    // implement the Actor trait
    impl Actor for MyActor {
        type Msg = String;
    
        fn recv(&mut self,
                    _ctx: &Context<String>,
                    msg: String,
                    _sender: Sender) {
    
            println!("Received: {}", msg);
        }
    }
    
    // start the system and create an actor
    fn main() {
        let sys = ActorSystem::new().unwrap();
    
        let my_actor = sys.actor_of::<MyActor>("my-actor").unwrap();
    
        my_actor.tell("Hello my actor!".to_string(), None);
    
        std::thread::sleep(Duration::from_millis(500));
    }
  3. How to run pre-commit hooks in Riker

    master

    Riker uses pre-commit as a git hook to automatically check code. It is recommended not to skip these hooks. You can run them using the following methods:

    Direct approach:

    pre-commit run -a

    Using yarn or npm:

    yarn
    yarn lint
    npm run install
    npn run lint
  4. Install and use Riker Macros

    master

    The riker-macros crate provides procedural macros for the Riker Actor Framework. To use these macros in your project, include riker-macros as a dependency in your Cargo.toml file. These macros are designed to work alongside the core riker crate to simplify actor definitions and framework integration.

    [dependencies]
    riker-macros = "..."
  5. What is an ActorSelection and when to use it

    master

    An ActorSelection represents a segment of the actor hierarchy, allowing you to send messages to groups of actors based on a path.

    Use Cases

    • Path-based addressing: You know the path of an actor but do not have its specific ActorRef.
    • Broadcasting: You want to send a message to all actors within a specific path (e.g., all children of a certain node).

    Important Considerations

    • Performance: ActorRef is preferred for direct interaction because messages are sent directly to the mailbox without preprocessing. ActorSelection requires traversing the hierarchy and cloning messages.
    • Typing: Because a selection can target multiple actors, messaging via try_tell() is untyped. Messages not supported by an actor in the selection will be silently dropped.
    • Anchoring: A selection is anchored to an ActorRef, and the provided path is relative to that anchor's path.
  6. Define and use messages in Riker

    master

    In Riker, any type that implements Debug + Clone + Send + 'static can be treated as a Message. The library provides a blanket implementation for these traits, so you can use most standard types as messages directly.

    When sending messages, they are often wrapped in an Envelope<T>, which contains the message itself and an optional sender (BasicActorRef) to allow the recipient to reply.

    use riker::{Message, Envelope, actor::BasicActorRef};
    
    #[derive(Debug, Clone)]
    struct MyMessage(String);
    
    // MyMessage automatically implements Message via blanket impl
    
    let envelope = Envelope {
        sender: Some(some_actor_ref),
        msg: MyMessage("Hello".to_string()),
    };
  7. How to create actor configuration with Props

    master

    In Riker, actors are not instantiated directly. Instead, you use Props to create an ActorProducer. This producer is passed to the ActorSystem (via actor_of_props), allowing the system to manage the actor's lifecycle, including initial creation and restarts during supervision.

    Props provides several ways to define how an actor is constructed, depending on whether the actor requires parameters or implements specific factory traits.

    // Basic pattern:
    let props = Props::new_from(User::actor);
    let actor = sys.actor_of_props("user", props).unwrap();
  8. Understand SystemMsg, SystemCmd, and SystemEvent

    master

    Riker uses a hierarchy of types to communicate system-level changes and commands:

    • SystemMsg: The top-level envelope for system communication. It can contain a Command, an Event, or a Failed status.
    • SystemCmd: Commands sent to the system, specifically Stop and Restart.
    • SystemEvent: Lifecycle notifications emitted by the system:
      • ActorCreated(ActorCreated)
      • ActorRestarted(ActorRestarted)
      • ActorTerminated(ActorTerminated)

    These types implement Into<SystemMsg>, allowing you to easily wrap commands or events into a system message.

  9. Manage actor lifecycle with Context

    master

    In Riker, the Context<Msg> struct is provided to an actor during its execution (e.g., within the receive method). It serves as the primary interface for an actor to interact with the system and manage its own lifecycle and children.

    Key capabilities include:

    • Creating children: Use actor_of, actor_of_props, or actor_of_args to spawn new actors under the current actor's hierarchy.
    • Scheduling tasks: Use the Timer implementation on Context to schedule one-off or repeating messages.
    • Actor Selection: Use select(path) to find an actor by its URI path.
    • System Access: Access the underlying ActorSystem via .system().

    Note: Context is specific to an actor's execution and is not cloneable.

    // Example of using Context within an actor implementation
    // (Conceptual usage based on Context capabilities)
    impl Actor for MyActor {
        type Msg = MyMessage;
    
        fn receive(&mut self, ctx: &mut Context<Self::Msg>, msg: Self::Msg) {
            match msg {
                MyMessage::SpawnChild => {
                    let child = ctx.actor_of::<ChildActor>("child_name").unwrap();
                    // ...
                }
                MyMessage::ScheduleTask => {
                    ctx.schedule_once(Duration::from_secs(5), ctx.myself(), None, MyMessage::DelayedTask);
                }
            }
        }
    }
  10. Use BasicActorRef for untyped messaging

    master

    A BasicActorRef is a lightweight, un-typed reference to an actor. It is useful when:

    • The original ActorRef<Msg> is unavailable.
    • You need to store references to different actor types in a single collection.
    • You are using actor selections to message parts of the hierarchy.

    Unlike ActorRef, BasicActorRef uses try_tell for messaging. This returns a Result<(), AnyEnqueueError>, where an error indicates the message type was not supported by the target actor.

    You can convert an ActorRef<Msg> to a BasicActorRef using From or the .typed() method to regain type safety.

    // Converting from a typed ActorRef to a BasicActorRef
    let basic_ref: BasicActorRef = ActorRef::from(typed_actor);
    
    // Sending an untyped message
    let result = basic_ref.try_tell(MyMessage::Hello, None);
  11. Define and use Topics in Channels

    master

    A Topic is a wrapper around a String used to filter messages within a channel.

    Creating Topics

    • From a string slice: Topic::from("my-topic")
    • From a String: Topic::from(some_string)
    • From a SystemEvent: Topic::from(&SystemEvent::ActorCreated(...))
    • From SysTopic: Topic::from(SysTopic::ActorTerminated)

    Special Topics

    • All Topic: To subscribe to every message published on a channel, use the All struct, which converts to a topic with the value "*".
  12. Configure actor supervision strategies

    master

    Actors can define how their failures are handled by returning a Strategy from the supervisor_strategy method. This strategy is used by the parent actor when a child actor fails.

    Available strategies:

    • Strategy::Stop: Stop the child actor.
    • Strategy::Restart: Attempt to restart the child actor.
    • Strategy::Escalate: Escalate the failure to the parent actor.
    fn supervisor_strategy(&self) -> Strategy {
        Strategy::Restart
    }