Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Creating Service Definitions

Relevant source files

Purpose and Scope

This page provides a step-by-step guide to creating RPC service definitions by implementing the RpcMethodPrebuffered trait. Service definitions establish type-safe contracts between clients and servers, ensuring that both sides use identical data structures and serialization logic at compile time.

For conceptual background on the RpcMethodPrebuffered trait and its role in the architecture, see Service Definitions. For details on compile-time method ID generation, see Method ID Generation. For serialization internals, see Serialization with Bitcode.

Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-97 extensions/muxio-rpc-service/Cargo.toml:1-18


Service Definition Structure

A service definition is a Rust struct that implements RpcMethodPrebuffered. Each struct represents a single RPC method and defines:

ComponentPurposeCompile-Time or Runtime
METHOD_IDUnique identifier for the methodCompile-time constant
Input typeParameter structureCompile-time type
Output typeResponse structureCompile-time type
encode_requestSerializes parameters to bytesRuntime function
decode_requestDeserializes parameters from bytesRuntime function
encode_responseSerializes result to bytesRuntime function
decode_responseDeserializes result from bytesRuntime function

Sources: extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:10-21 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:36-60


Required Trait Implementation

The RpcMethodPrebuffered trait is defined in muxio-rpc-service and must be implemented for each RPC method. The trait definition looks like this:

Associated Types

TypeRequirementsPurpose
InputSerialize + DeserializeOwned + Send + SyncMethod parameter type
OutputSerialize + DeserializeOwned + Send + SyncMethod return type

Sources: extensions/muxio-rpc-service/Cargo.toml:11-17


Step-by-Step Implementation

1. Create a Service Definition Crate

Service definitions should live in a separate crate that both client and server depend on. This ensures compile-time type safety across the network boundary.

example-muxio-rpc-service-definition/
├── Cargo.toml
└── src/
    ├── lib.rs
    └── prebuffered.rs

The Cargo.toml must include:

  • muxio-rpc-service (provides RpcMethodPrebuffered trait)
  • bitcode (for serialization)
  • xxhash-rust (for method ID generation)

Sources: extensions/muxio-rpc-service/Cargo.toml:11-17

2. Define the Method Struct

Create a zero-sized struct for each RPC method:

These structs have no fields and exist purely to carry trait implementations.

Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs1

3. Generate METHOD_ID

The METHOD_ID is a compile-time constant generated by hashing the method name. This must be unique across all methods in your service:

The hash function is xxhash_rust::const_xxh3::xxh3_64, which can be evaluated at compile time. Using the method name as the hash input ensures readability while maintaining uniqueness.

Sources: extensions/muxio-rpc-service/Cargo.toml16

4. Implement RpcMethodPrebuffered

Sources: extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:23-29 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:38-41


graph LR
    subgraph "Request Path"
        INPUT["Input: Vec<f64>"]
ENC_REQ["encode_request"]
BYTES_REQ["Vec<u8>"]
DEC_REQ["decode_request"]
INPUT2["Input: Vec<f64>"]
end
    
    subgraph "Response Path"
        OUTPUT["Output: f64"]
ENC_RES["encode_response"]
BYTES_RES["Vec<u8>"]
DEC_RES["decode_response"]
OUTPUT2["Output: f64"]
end
    
 
   INPUT --> ENC_REQ
 
   ENC_REQ --> BYTES_REQ
 
   BYTES_REQ --> DEC_REQ
 
   DEC_REQ --> INPUT2
    
 
   OUTPUT --> ENC_RES
 
   ENC_RES --> BYTES_RES
 
   BYTES_RES --> DEC_RES
 
   DEC_RES --> OUTPUT2

Serialization Implementation

The encode/decode methods use bitcode for binary serialization. The pattern is consistent across all methods:

Error Handling

All encode/decode methods return Result<T, io::Error>. The bitcode library's errors must be converted to io::Error:

Sources: extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:75-76


Client and Server Usage

Once a service definition is implemented, both client and server use it to ensure type safety.

Client-Side Usage

The RpcCallPrebuffered trait provides the call method automatically for any type implementing RpcMethodPrebuffered.

Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:82-88

Server-Side Usage

The handler receives raw bytes, uses the service definition to decode them, processes the request, and uses the service definition to encode the response.

Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:35-61


Code Entity Mapping

Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:35-61 extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:49-97


Complete Example: Echo Service

Here is a complete implementation of an Echo service that returns its input unchanged:

Client Usage

Server Usage

Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:53-60 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:86-87


Handling Large Payloads

The RpcCallPrebuffered trait automatically handles large payloads by detecting when encoded arguments exceed DEFAULT_SERVICE_MAX_CHUNK_SIZE (typically 64KB). When this occurs:

  1. Small payloads: Encoded arguments are placed in rpc_param_bytes field of the request header
  2. Large payloads: Encoded arguments are placed in rpc_prebuffered_payload_bytes and automatically chunked by the RpcDispatcher

This is handled transparently by the framework. Service definitions do not need special logic for large payloads.

Sources: extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:30-48 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:155-203


Best Practices

1. Use Separate Service Definition Crates

Create a dedicated crate for service definitions that both client and server depend on:

workspace/
├── my-service-definition/  # Shared definitions
├── my-server/             # Depends on my-service-definition
└── my-client/             # Depends on my-service-definition

2. Choose Descriptive Method Names

The METHOD_ID is generated from the method name, so choose names that clearly describe the operation:

3. Keep Input/Output Types Simple

Prefer simple, serializable types:

4. Test Service Definitions

Integration tests should verify both client and server usage:

Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:16-97 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:39-142

5. Document Expected Errors

Service definitions should document what errors can occur during encode/decode:

Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:99-152


Cross-Platform Compatibility

Service definitions work identically across native (Tokio) and WebAssembly clients:

PlatformClient TypeService Definition Usage
Nativemuxio-tokio-rpc-client::RpcClientAdd::call(client.as_ref(), params).await
WASMmuxio-wasm-rpc-client::RpcWasmClientAdd::call(client.as_ref(), params).await

Both platforms use the same service definition crate, ensuring API consistency across deployments.

Sources: extensions/muxio-wasm-rpc-client/Cargo.toml:11-22 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:126-142

Dismiss

Refresh this wiki

Enter email to refresh