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.

Service Definitions

Loading…

Service Definitions

Relevant source files

Purpose and Scope

Service definitions provide compile-time type-safe RPC method contracts that are shared between client and server implementations. The muxio-rpc-service crate defines the core traits and utilities for declaring RPC methods with automatic method ID generation and efficient binary serialization. Service definitions serve as the single source of truth for RPC interfaces, ensuring that both sides of the communication agree on method signatures, parameter types, and return types at compile time.

For information about implementing client-side invocation logic, see Service Caller Interface. For server-side handler registration, see Service Endpoint Interface. For a step-by-step guide on creating your own service definitions, see Creating Service Definitions.


Core Architecture

The service definition layer sits at the top of the RPC abstraction layer, providing the foundation for type-safe communication. It bridges application-level Rust types with the underlying binary protocol.

Sources:

graph TB
    subgraph "Application Layer"
        APP["Application Code\nBusiness Logic"]
end
    
    subgraph "Service Definition Layer"
        TRAIT["RpcMethodPrebuffered Trait\nMethod Signature Declaration"]
METHODID["METHOD_ID Constant\nxxhash::xxh3_64(method_name)"]
PARAMS["Parameter Types\nSerialize + Deserialize"]
RESULT["Result Types\nSerialize + Deserialize"]
end
    
    subgraph "RPC Framework Layer"
        CALLER["RpcServiceCallerInterface\nClient Invocation"]
ENDPOINT["RpcServiceEndpointInterface\nServer Dispatch"]
SERIALIZER["bitcode::encode/decode\nBinary Serialization"]
end
    
 
   APP --> TRAIT
 
   TRAIT --> METHODID
 
   TRAIT --> PARAMS
 
   TRAIT --> RESULT
 
   CALLER --> METHODID
 
   ENDPOINT --> METHODID
 
   PARAMS --> SERIALIZER
 
   RESULT --> SERIALIZER

The muxio-rpc-service Crate

The muxio-rpc-service crate provides the foundational types and traits for defining RPC services. It has minimal dependencies to remain runtime-agnostic and platform-independent.

Dependencies

DependencyPurpose
async-traitEnables async trait methods for service definitions
futuresProvides stream abstractions for streaming RPC methods
muxioCore framing and multiplexing primitives
num_enumDiscriminated union encoding for message types
xxhash-rustFast compile-time hash generation for method IDs
bitcodeCompact binary serialization for parameters and results

Sources:


The RpcMethodPrebuffered Trait

The RpcMethodPrebuffered trait is the primary mechanism for defining RPC methods. It specifies the method signature, parameter types, result types, and automatically generates a unique method identifier.

graph LR
    subgraph "RpcMethodPrebuffered Trait Definition"
        NAME["const NAME: &'static str\nHuman-readable method name"]
METHODID["const METHOD_ID: u64\nxxh3_64(NAME)
at compile time"]
PARAMS["type Params\nSerialize + Deserialize + Send"]
RESULT["type Result\nSerialize + Deserialize + Send"]
end
    
    subgraph "Example: AddMethod"
        NAME_EX["NAME = 'Add'"]
METHODID_EX["METHOD_ID = 0x5f8b3c4a2e1d6f90"]
PARAMS_EX["Params = (i32, i32)"]
RESULT_EX["Result = i32"]
end
    
 
   NAME --> NAME_EX
 
   METHODID --> METHODID_EX
 
   PARAMS --> PARAMS_EX
 
   RESULT --> RESULT_EX

Key Components

ComponentTypeDescription
NAMEconst &'static strHuman-readable method name (e.g., “Add”, “Multiply”)
METHOD_IDconst u64Compile-time hash of NAME using xxhash
ParamsAssociated TypeInput parameter type, must implement Serialize + Deserialize + Send
ResultAssociated TypeReturn value type, must implement Serialize + Deserialize + Send

Type Safety Guarantees

Service definitions enforce several type safety invariants:

  1. Compile-time method identification : Method IDs are computed at compile time from method names
  2. Consistent serialization : Both client and server use the same bitcode schema for parameters
  3. Type mismatch detection : Mismatched parameter or result types cause compilation errors
  4. Zero-cost abstraction : Method dispatch has no runtime overhead beyond the hash lookup

Sources:


Method ID Generation with xxhash

Method IDs are 64-bit unsigned integers generated at compile time by hashing the method name. This approach provides efficient dispatch while maintaining human-readable method names in code.

graph LR
    subgraph "Compile Time"
        METHNAME["Method Name (String)\ne.g., 'Add', 'Multiply', 'Echo'"]
HASH["xxhash::xxh3_64(bytes)"]
METHODID["METHOD_ID: u64\ne.g., 0x5f8b3c4a2e1d6f90"]
METHNAME --> HASH
 
       HASH --> METHODID
    end
    
    subgraph "Runtime - Client"
        CLIENT_CALL["Client calls method"]
CLIENT_ENCODE["RpcRequest::method_id = METHOD_ID"]
CLIENT_SEND["Send binary request"]
CLIENT_CALL --> CLIENT_ENCODE
 
       CLIENT_ENCODE --> CLIENT_SEND
    end
    
    subgraph "Runtime - Server"
        SERVER_RECV["Receive binary request"]
SERVER_DECODE["Extract method_id from RpcRequest"]
SERVER_MATCH["Match method_id to handler"]
SERVER_EXEC["Execute handler"]
SERVER_RECV --> SERVER_DECODE
 
       SERVER_DECODE --> SERVER_MATCH
 
       SERVER_MATCH --> SERVER_EXEC
    end
    
 
   METHODID --> CLIENT_ENCODE
 
   METHODID --> SERVER_MATCH

Method ID Properties

PropertyDescription
Size64-bit unsigned integer
GenerationCompile-time hash using xxh3_64 algorithm
Collision ResistanceExtremely low probability of collision for reasonable method counts
PerformanceSingle integer comparison for method dispatch
StabilitySame method name always produces same ID across compilations

Benefits of Compile-Time Method IDs

  1. No string comparison overhead : Method dispatch uses integer comparison instead of string matching
  2. Compact wire format : Only 8 bytes sent over the network instead of method name strings
  3. Automatic generation : No manual assignment of method IDs required
  4. Type-safe verification : Compile-time guarantee that client and server agree on method IDs

Sources:


graph TB
    subgraph "Parameter Encoding"
        RUSTPARAM["Rust Type\ne.g., (i32, String, Vec<u8>)"]
BITCODEENC["bitcode::encode(params)"]
BINARY["Compact Binary Payload\nVariable-length encoding"]
RUSTPARAM --> BITCODEENC
 
       BITCODEENC --> BINARY
    end
    
    subgraph "RPC Request Structure"
        RPCREQ["RpcRequest"]
REQMETHOD["method_id: u64"]
REQPARAMS["params: Vec<u8>"]
RPCREQ --> REQMETHOD
 
       RPCREQ --> REQPARAMS
    end
    
    subgraph "Result Decoding"
        RESPBINARY["Binary Payload"]
BITCODEDEC["bitcode::decode::<T>(bytes)"]
RUSTRESULT["Rust Type\ne.g., Result<String, Error>"]
RESPBINARY --> BITCODEDEC
 
       BITCODEDEC --> RUSTRESULT
    end
    
 
   BINARY --> REQPARAMS
    REQPARAMS -.Wire Protocol.-> RESPBINARY

Serialization with Bitcode

All RPC parameters and results are serialized using the bitcode crate, which provides compact binary encoding with built-in support for Rust types.

Bitcode Characteristics

CharacteristicDescription
EncodingCompact binary format with variable-length integers
SchemaSchemaless - structure implied by Rust types
PerformanceZero-copy deserialization where possible
Type SupportBuilt-in support for standard Rust types (primitives, tuples, Vec, HashMap, etc.)
VersioningField order and type changes require coordinated updates

Serialization Requirements

For a type to be used as Params or Result in an RPC method definition, it must implement:

Serialize + Deserialize + Send

These bounds ensure:

  • The type can be encoded to binary (Serialize)
  • The type can be decoded from binary (Deserialize)
  • The type can be safely sent across thread boundaries (Send)

Sources:


graph TB
    subgraph "Service Definition Crate"
        CRATE["example-muxio-rpc-service-definition"]
subgraph "Method Definitions"
            ADD["AddMethod\nNAME: 'Add'\nParams: (i32, i32)\nResult: i32"]
MULT["MultiplyMethod\nNAME: 'Multiply'\nParams: (i32, i32)\nResult: i32"]
ECHO["EchoMethod\nNAME: 'Echo'\nParams: String\nResult: String"]
end
        
 
       CRATE --> ADD
 
       CRATE --> MULT
 
       CRATE --> ECHO
    end
    
    subgraph "Consumer Crates"
        CLIENT["Client Application\nUses methods via RpcServiceCallerInterface"]
SERVER["Server Application\nImplements handlers via RpcServiceEndpointInterface"]
end
    
 
   ADD --> CLIENT
 
   MULT --> CLIENT
 
   ECHO --> CLIENT
 
   ADD --> SERVER
 
   MULT --> SERVER
 
   ECHO --> SERVER

Service Definition Structure

A complete service definition typically consists of multiple method trait implementations grouped together. Here’s the conceptual structure:

Typical Crate Layout

example-muxio-rpc-service-definition/
├── Cargo.toml
│   ├── [dependency] muxio-rpc-service
│   └── [dependency] bitcode
└── src/
    └── lib.rs
        ├── struct AddMethod;
        ├── impl RpcMethodPrebuffered for AddMethod { ... }
        ├── struct MultiplyMethod;
        ├── impl RpcMethodPrebuffered for MultiplyMethod { ... }
        └── ...

Sources:


graph TB
    subgraph "Service Definition"
        SERVICEDEF["RpcMethodPrebuffered Implementation\n- NAME\n- METHOD_ID\n- Params\n- Result"]
end
    
    subgraph "Client Side"
        CALLERIFACE["RpcServiceCallerInterface"]
CALLER_INVOKE["call_method<<M: RpcMethodPrebuffered>>()"]
DISPATCHER["RpcDispatcher"]
CALLERIFACE --> CALLER_INVOKE
 
       CALLER_INVOKE --> DISPATCHER
    end
    
    subgraph "Server Side"
        ENDPOINTIFACE["RpcServiceEndpointInterface"]
ENDPOINT_REGISTER["register<<M: RpcMethodPrebuffered>>()"]
HANDLER_MAP["HashMap<u64, Handler>"]
ENDPOINTIFACE --> ENDPOINT_REGISTER
 
       ENDPOINT_REGISTER --> HANDLER_MAP
    end
    
    subgraph "Wire Protocol"
        RPCREQUEST["RpcRequest\nmethod_id: u64\nparams: Vec<u8>"]
RPCRESPONSE["RpcResponse\nrequest_id: u64\nresult: Vec<u8>"]
end
    
 
   SERVICEDEF --> CALLER_INVOKE
 
   SERVICEDEF --> ENDPOINT_REGISTER
 
   DISPATCHER --> RPCREQUEST
 
   RPCREQUEST --> HANDLER_MAP
 
   HANDLER_MAP --> RPCRESPONSE
 
   RPCRESPONSE --> DISPATCHER

Integration with the RPC Framework

Service definitions integrate with the broader RPC framework through well-defined interfaces:

Compile-Time Guarantees

The service definition system provides several compile-time guarantees:

GuaranteeMechanism
Type SafetyGeneric trait bounds enforce matching types across client/server
Method ID UniquenessHashing function produces consistent IDs for method names
Serialization CompatibilityShared trait implementations ensure same encoding/decoding
Parameter ValidationRust type system validates parameter structure at compile time

Runtime Flow

  1. Client : Invokes method through RpcServiceCallerInterface::call::<MethodType>(params)
  2. Serialization : Parameters are encoded using bitcode::encode(params)
  3. Request Construction : RpcRequest created with METHOD_ID and serialized params
  4. Server Dispatch : Request routed to handler based on method_id lookup
  5. Handler Execution : Handler deserializes params, executes logic, serializes result
  6. Response Delivery : RpcResponse sent back with serialized result
  7. Client Deserialization : Result decoded using bitcode::decode::<ResultType>(bytes)

Sources:


Cross-Platform Compatibility

Service definitions are completely platform-agnostic. The same service definition crate can be used by:

  • Native Tokio-based clients and servers
  • WASM browser-based clients
  • Custom transport implementations
  • Different runtime environments (async/sync)

This cross-platform capability is achieved because:

  • Service definitions contain no platform-specific code
  • Serialization is handled by platform-agnostic bitcode
  • Method IDs are computed at compile time without runtime dependencies
  • The trait system provides compile-time polymorphism

Sources:


Summary

Service definitions in muxio provide:

  1. Type-Safe Contracts : Compile-time verified method signatures shared between client and server
  2. Efficient Dispatch : 64-bit integer method IDs computed at compile time using xxhash
  3. Compact Serialization : Binary encoding using bitcode for minimal network overhead
  4. Platform Independence : Service definitions work across native, WASM, and custom platforms
  5. Zero Runtime Cost : All method resolution and type checking happens at compile time

The next sections cover how to use service definitions from the client side (Service Caller Interface) and server side (Service Endpoint Interface), as well as the specific patterns for prebuffered (Prebuffered RPC Calls) and streaming (Streaming RPC Calls) RPC methods.

Sources: