Trait relay_system::Service

source ·
pub trait Service: Sized {
    type Interface: Interface;

    // Required method
    fn spawn_handler(self, rx: Receiver<Self::Interface>);

    // Provided methods
    fn start(self) -> Addr<Self::Interface> { ... }
    fn start_in(self, runtime: &Runtime) -> Addr<Self::Interface> { ... }
    fn name() -> &'static str { ... }
}
Expand description

An asynchronous unit responding to messages.

Services receive messages conforming to some Interface through an Addr and handle them one by one. Internally, services are free to concurrently process these messages or not, most probably should.

Individual messages can have a response which will be sent once the message is handled by the service. The sender can asynchronously await the responses of such messages.

To start a service, create an instance of the service and use Service::start.

§Implementing Services

The standard way to implement services is through the run function. It receives an inbound channel for all messages sent through the service’s address. Note that this function is synchronous, so that this needs to spawn at least one task internally:

use relay_system::{FromMessage, Interface, NoResponse, Receiver, Service};

struct MyMessage;

impl Interface for MyMessage {}

impl FromMessage<Self> for MyMessage {
    type Response = NoResponse;

    fn from_message(message: Self, _: ()) -> Self {
        message
    }
}

struct MyService;

impl Service for MyService {
    type Interface = MyMessage;

    fn spawn_handler(self, mut rx: Receiver<Self::Interface>) {
        tokio::spawn(async move {
            while let Some(message) = rx.recv().await {
                // handle the message
            }
        });
    }
}

let addr = MyService.start();

§Debounce and Caching

Services that cache or debounce their responses can benefit from the BroadcastResponse behavior. To use this behavior, implement the message and interface identical to AsyncResponse above. This will provide a different sender type that can be converted into a channel to debounce responses. It is still possible to send values directly via the sender without a broadcast channel.

use std::collections::btree_map::{BTreeMap, Entry};
use relay_system::{BroadcastChannel, BroadcastSender};

// FromMessage implementation using BroadcastResponse omitted for brevity.

struct MyService {
    cache: BTreeMap<u32, String>,
    channels: BTreeMap<u32, BroadcastChannel<String>>,
}

impl MyService {
    fn handle_message(&mut self, id: u32, sender: BroadcastSender<String>) {
        if let Some(cached) = self.cache.get(&id) {
            sender.send(cached.clone());
            return;
        }

        match self.channels.entry(id) {
            Entry::Vacant(entry) => {
                entry.insert(sender.into_channel());
                // Start async computation here.
            }
            Entry::Occupied(mut entry) => {
                entry.get_mut().attach(sender);
            }
        }
    }

    fn finish_compute(&mut self, id: u32, value: String) {
        if let Some(channel) = self.channels.remove(&id) {
            channel.send(value.clone());
        }

        self.cache.insert(id, value);
    }
}

Required Associated Types§

source

type Interface: Interface

The interface of messages this service implements.

The interface can be a single message type or an enumeration of all the messages that can be handled by this service.

Required Methods§

source

fn spawn_handler(self, rx: Receiver<Self::Interface>)

Spawns a task to handle service messages.

Receives an inbound channel for all messages sent through the service’s Addr. Note that this function is synchronous, so that this needs to spawn a task internally.

Provided Methods§

source

fn start(self) -> Addr<Self::Interface>

Starts the service in the current runtime and returns an address for it.

source

fn start_in(self, runtime: &Runtime) -> Addr<Self::Interface>

Starts the service in the given runtime and returns an address for it.

source

fn name() -> &'static str

Returns a unique name for this service implementation.

This is used for internal diagnostics and uses the fully qualified type name of the service implementor by default.

Object Safety§

This trait is not object safe.

Implementors§