This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Service Definitions
Relevant source files
- README.md
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs
- extensions/muxio-rpc-service/Cargo.toml
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs
- extensions/muxio-wasm-rpc-client/Cargo.toml
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs
Purpose and Scope
This document explains how RPC service definitions are created and shared in the rust-muxio system. Service definitions are the contracts that define RPC methods, their inputs, outputs, and unique identifiers. By implementing the RpcMethodPrebuffered trait, both clients and servers depend on the same type-safe API contract, eliminating an entire class of distributed system bugs through compile-time verification.
For information about how clients invoke these definitions, see Service Caller Interface. For information about how servers handle these definitions, see Service Endpoint Interface.
Sources: README.md:16-50
The RpcMethodPrebuffered Trait
The RpcMethodPrebuffered trait is the foundational abstraction for defining RPC methods in muxio. Each RPC method is a type that implements this trait, specifying the method's unique identifier, input type, output type, and serialization logic.
classDiagram
class RpcMethodPrebuffered {
<<trait>>
+METHOD_ID: u64\n+Input: type\n+Output: type
+encode_request(input: Input) Result~Vec~u8~, io::Error~
+decode_request(bytes: &[u8]) Result~Input, io::Error~
+encode_response(output: Output) Result~Vec~u8~, io::Error~
+decode_response(bytes: &[u8]) Result~Output, io::Error~
}
class Add {+METHOD_ID: u64\n+Input: Vec~f64~\n+Output: f64}
class Mult {+METHOD_ID: u64\n+Input: Vec~f64~\n+Output: f64}
class Echo {+METHOD_ID: u64\n+Input: Vec~u8~\n+Output: Vec~u8~}
RpcMethodPrebuffered <|.. Add
RpcMethodPrebuffered <|.. Mult
RpcMethodPrebuffered <|.. Echo
Trait Structure
Each concrete implementation (like Add, Mult, Echo) must provide:
| Component | Type | Purpose |
|---|---|---|
METHOD_ID | u64 | Compile-time generated unique identifier for the method |
Input | Associated type | The Rust type representing the method's parameters |
Output | Associated type | The Rust type representing the method's return value |
encode_request | Function | Serializes Input to Vec<u8> |
decode_request | Function | Deserializes Vec<u8> to Input |
encode_response | Function | Serializes Output to Vec<u8> |
decode_response | Function | Deserializes Vec<u8> to Output |
Sources: README.md49 extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:1-28
Method ID Generation
Each RPC method is assigned a unique METHOD_ID at compile time. This identifier is generated by hashing the method's name using the xxhash-rust library. The hash-based approach ensures that method IDs are deterministic, collision-resistant, and do not require manual coordination.
graph LR
MethodName["Method Name\n(e.g., 'Add')"]
Hash["xxhash-rust\nHash Function"]
MethodID["METHOD_ID\n(u64 constant)"]
CompileTime["Compile Time\nConstant"]
MethodName --> Hash
Hash --> MethodID
MethodID --> CompileTime
CompileTime -.enforces.-> UniqueID["Unique Identifier\nper Method"]
CompileTime -.enables.-> TypeSafety["Compile-time\nCollision Detection"]
Hash-Based Identification
The METHOD_ID is computed once at compile time and stored as a constant. This approach provides several benefits:
- Deterministic : The same method name always produces the same ID across all builds
- Collision-Resistant : The 64-bit hash space makes accidental collisions extremely unlikely
- Zero Runtime Overhead : No string comparisons or lookups needed during RPC dispatch
- Type-Safe : Method IDs are baked into the type system, preventing runtime mismatches
When a client makes an RPC call using Add::call(), it automatically includes Add::METHOD_ID in the request. When the server receives this request, it uses the method ID to route to the correct handler.
Sources: README.md49 extensions/muxio-rpc-service/Cargo.toml16 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:35-36
Encoding and Decoding
Service definitions are responsible for converting between typed Rust structures and binary representations. The trait defines four serialization methods that operate on raw bytes:
graph LR
subgraph "Client Side"
Input1["Input\n(Typed Struct)"]
EncReq["encode_request()"]
ReqBytes["Request Bytes\n(Vec<u8>)"]
end
subgraph "Transport"
Network["Binary\nNetwork Frames"]
end
subgraph "Server Side"
ReqBytes2["Request Bytes\n(Vec<u8>)"]
DecReq["decode_request()"]
Input2["Input\n(Typed Struct)"]
end
Input1 --> EncReq
EncReq --> ReqBytes
ReqBytes --> Network
Network --> ReqBytes2
ReqBytes2 --> DecReq
DecReq --> Input2
Request Serialization Flow
Response Serialization Flow
Serialization Implementation
While service definitions can use any serialization format, the system commonly uses the bitcode library for efficient binary serialization. This provides:
- Compact binary representation (smaller than JSON or MessagePack)
- Fast encoding/decoding performance
- Native Rust type support without manual schema definitions
The separation of serialization logic into the service definition ensures that both client and server use identical encoding/decoding logic, preventing deserialization mismatches.
Sources: extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:55-76 extensions/muxio-rpc-service/Cargo.toml17
graph TB
subgraph "Shared Service Definition Crate"
AddDef["Add Method\nimpl RpcMethodPrebuffered"]
MultDef["Mult Method\nimpl RpcMethodPrebuffered"]
EchoDef["Echo Method\nimpl RpcMethodPrebuffered"]
end
subgraph "Client Implementation"
TokioClient["muxio-tokio-rpc-client"]
WasmClient["muxio-wasm-rpc-client"]
AddCall["Add::call()"]
MultCall["Mult::call()"]
end
subgraph "Server Implementation"
TokioServer["muxio-tokio-rpc-server"]
AddHandler["Add Handler\nregister_prebuffered()"]
MultHandler["Mult Handler\nregister_prebuffered()"]
end
AddDef -.depends on.-> TokioClient
AddDef -.depends on.-> WasmClient
AddDef -.depends on.-> TokioServer
MultDef -.depends on.-> TokioClient
MultDef -.depends on.-> WasmClient
MultDef -.depends on.-> TokioServer
AddDef --> AddCall
AddDef --> AddHandler
MultDef --> MultCall
MultDef --> MultHandler
Shared Definitions Pattern
The key architectural principle is that service definitions are placed in a shared crate that both client and server implementations depend on. This shared dependency enforces API contracts at compile time.
Example Usage Pattern
The integration tests demonstrate this pattern in practice:
Client-side invocation:
Server-side handler registration:
Both client and server use:
Add::METHOD_IDfor routingAdd::decode_request()for parsing inputsAdd::encode_response()for formatting outputs
Sources: README.md49 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-96 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:21-142
Type Safety Guarantees
Service definitions provide compile-time guarantees that prevent an entire class of distributed system bugs. Any mismatch between client and server results in a compilation error rather than a runtime failure.
Compile-Time Error Scenarios
| Scenario | Error Type | Detection Point |
|---|---|---|
Client and server use different Input types | Type mismatch | Compile time |
Client and server use different Output types | Type mismatch | Compile time |
| Adding/removing fields from request/response | Deserialization error | Compile time (via shared definition) |
| Using wrong method ID for handler | Method not found | Compile time (via shared constant) |
| Duplicate method names | Hash collision | Compile time (deterministic hashing) |
Example: Preventing Type Mismatches
If a developer attempts to change the Add method's input type on the server without updating the shared definition:
- The server code references
Add::decode_request()from the shared definition - The server handler expects the original
Vec<f64>type - Any type mismatch produces a compile error
- The server cannot be built until the shared definition is updated
- Once updated, all clients must also be rebuilt, automatically receiving the new type
This eliminates scenarios where:
- A client sends the wrong data format
- A server expects a different response structure
- Method IDs collide between different methods
- Serialization logic differs between client and server
Sources: README.md49 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:82-95
graph TB
App["Application Code"]
ServiceDef["Service Definition\n(RpcMethodPrebuffered)"]
CallTrait["RpcCallPrebuffered Trait"]
CallerInterface["RpcServiceCallerInterface"]
Transport["Transport\n(Tokio/WASM)"]
App -->|Add::call client, input| CallTrait
CallTrait -->|uses| ServiceDef
CallTrait -->|encode_request| ServiceDef
CallTrait -->|decode_response| ServiceDef
CallTrait -->|METHOD_ID| ServiceDef
CallTrait -->|call_rpc_buffered| CallerInterface
CallerInterface --> Transport
Integration with RPC Framework
Service definitions integrate with the broader RPC framework through the RpcCallPrebuffered trait, which provides the high-level call() method that applications use.
graph TB
subgraph "Trait Definition"
RpcCallPrebuffered["RpcCallPrebuffered\n(trait)"]
call_method["call()\n(async method)"]
end
subgraph "Trait Bounds"
RpcMethodPrebuffered["RpcMethodPrebuffered\n(provides encode/decode)"]
Send["Send + Sync\n(thread-safe)"]
sized["Sized\n(known size)"]
end
subgraph "Blanket Implementation"
blanket["impl<T> RpcCallPrebuffered for T\nwhere T: RpcMethodPrebuffered"]
end
subgraph "Example Types"
Add["Add\n(example service)"]
Mult["Mult\n(example service)"]
Echo["Echo\n(example service)"]
end
RpcCallPrebuffered --> call_method
blanket --> RpcCallPrebuffered
RpcMethodPrebuffered --> blanket
Send --> blanket
sized --> blanket
Add -.implements.-> RpcMethodPrebuffered
Mult -.implements.-> RpcMethodPrebuffered
Echo -.implements.-> RpcMethodPrebuffered
Add -.gets.-> RpcCallPrebuffered
Mult -.gets.-> RpcCallPrebuffered
Echo -.gets.-> RpcCallPrebuffered
The RpcCallPrebuffered trait is automatically implemented for any type that implements RpcMethodPrebuffered. This blanket implementation:
- Encodes the input using
encode_request() - Creates an
RpcRequestwith the encoded bytes andMETHOD_ID - Handles large argument payloads by routing them to the prebuffered payload field when needed
- Invokes the transport-specific caller interface
- Decodes the response using
decode_response() - Returns the typed result to the application
Sources: extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:10-98 README.md:100-118
graph TB
subgraph "example-muxio-rpc-service-definition"
Add["Add\nInput: Vec<f64>\nOutput: f64\nMETHOD_ID"]
Mult["Mult\nInput: Vec<f64>\nOutput: f64\nMETHOD_ID"]
Echo["Echo\nInput: Vec<u8>\nOutput: Vec<u8>\nMETHOD_ID"]
end
subgraph "Client Crates"
TokioClient["muxio-tokio-rpc-client"]
WasmClient["muxio-wasm-rpc-client"]
end
subgraph "Server Crate"
TokioServer["muxio-tokio-rpc-server"]
end
subgraph "Example Application"
WsApp["example-muxio-ws-rpc-app"]
end
Add --> TokioClient
Add --> WasmClient
Add --> TokioServer
Add --> WsApp
Mult --> TokioClient
Mult --> WasmClient
Mult --> TokioServer
Mult --> WsApp
Echo --> TokioClient
Echo --> WasmClient
Echo --> TokioServer
Echo --> WsApp
Example Service Definition Crate
The example-muxio-rpc-service-definition crate demonstrates the shared definition pattern:
This crate exports the method definitions that are used across:
- Native Tokio client tests
- WASM client tests
- Server implementations
- Example applications
All consumers share the exact same type definitions, method IDs, and serialization logic.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-6 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:21-27 README.md:70-73
Dismiss
Refresh this wiki
Enter email to refresh