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
- 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 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:
| Component | Purpose | Compile-Time or Runtime |
|---|---|---|
METHOD_ID | Unique identifier for the method | Compile-time constant |
Input type | Parameter structure | Compile-time type |
Output type | Response structure | Compile-time type |
encode_request | Serializes parameters to bytes | Runtime function |
decode_request | Deserializes parameters from bytes | Runtime function |
encode_response | Serializes result to bytes | Runtime function |
decode_response | Deserializes result from bytes | Runtime 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
| Type | Requirements | Purpose |
|---|---|---|
Input | Serialize + DeserializeOwned + Send + Sync | Method parameter type |
Output | Serialize + DeserializeOwned + Send + Sync | Method 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(providesRpcMethodPrebufferedtrait)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:
- Small payloads: Encoded arguments are placed in
rpc_param_bytesfield of the request header - Large payloads: Encoded arguments are placed in
rpc_prebuffered_payload_bytesand automatically chunked by theRpcDispatcher
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:
| Platform | Client Type | Service Definition Usage |
|---|---|---|
| Native | muxio-tokio-rpc-client::RpcClient | Add::call(client.as_ref(), params).await |
| WASM | muxio-wasm-rpc-client::RpcWasmClient | Add::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