Crate datacake_rpc

source ·
Expand description

An actor-like RPC framework built for true zero-copy message handling.

This framework is inspired by tonic but is not a GRPC framework. Instead, it makes use of the incredible rkyv (de)serialization framework which provides us with lightning fast (de)serialization and also lets us perform true zero-copy deserialization which can lead to massive performance improvements when processing lots of big messages at once.

Features

  • Fast (de)serialization of owned types.
  • True zero-copy deserialization avoiding heavy allocations.
  • Dynamic adding and removing of message handlers/services.

Basic example

use std::net::SocketAddr;

use datacake_rpc::{
    Channel,
    Handler,
    Request,
    RpcClient,
    RpcService,
    Server,
    ServiceRegistry,
    Status,
};
use rkyv::{Archive, Deserialize, Serialize};

// The framework accepts any messages which implement `Archive` and `Serialize` along
// with the archived values implementing `CheckBytes` from the `bytecheck` crate.
// This is to ensure safe, validated deserialization of the values.
//
// Checkout rkyv for more information!
#[repr(C)]
#[derive(Serialize, Deserialize, Archive, PartialEq, Debug)]
#[archive(compare(PartialEq), check_bytes)]
#[archive_attr(derive(PartialEq, Debug))]
pub struct MyMessage {
    name: String,
    age: u32,
}

pub struct MyService;

impl RpcService for MyService {
    // The `register_handlers` is used to mark messages as something
    // the given service can handle and process.
    //
    // Messages which are not registered will not be dispatched to the handler.
    fn register_handlers(registry: &mut ServiceRegistry<Self>) {
        registry.add_handler::<MyMessage>();
    }
}

#[datacake_rpc::async_trait]
impl Handler<MyMessage> for MyService {
    type Reply = String;

    // Our `Request` gives us a zero-copy view to our message, this doesn't actually
    // allocate the message type.
    async fn on_message(&self, msg: Request<MyMessage>) -> Result<Self::Reply, Status> {
        Ok(msg.to_owned().unwrap().name)
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let address = "127.0.0.1:8000".parse::<SocketAddr>()?;

    let server = Server::listen(address).await?;
    // Services can be added and removed at runtime once the server is started.
    server.add_service(MyService);
    println!("Listening to address {}!", address);

    // Channels are cheap to clone similar to tonic.
    let client = Channel::connect(address);
    println!("Connected to address {}!", address);

    let rpc_client = RpcClient::<MyService>::new(client);

    let msg1 = MyMessage {
        name: "Bobby".to_string(),
        age: 12,
    };

    // Clients only need references to the message which helps
    // reduce allocations.
    let resp = rpc_client.send(&msg1).await?;
    assert_eq!(resp, msg1.name);
    Ok(())
}

Structs

  • An archived Status
  • A wrapper type around the internal hyper::Body
  • A raw client connection which can produce multiplexed streams.
  • A block of data that can be accessed as if it is the archived value T.
  • The data provided is unable to be presented as the archived version of the view type.
  • A zero-copy view of the message data and any additional metadata provided by the RPC system.
  • A RPC client handle for a given service.
  • A RPC server instance.
  • A registry system used for linking a service’s message handlers with the RPC system at runtime.
  • Status information around the cause of a message request failing.

Enums

Traits

  • A generic RPC message handler.
  • The deserializer trait for converting the request body into the desired type specified by Self::Content.
  • A standard RPC server that handles messages.
  • The serializer trait for converting replies into hyper bodies using a reference to self.
  • The serializer trait converting replies into hyper bodies.

Type Definitions

  • A type alias for the returned data view of the RPC message reply.

Attribute Macros