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
Loading…
Creating Service Definitions
Relevant source files
- README.md
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs
Purpose and Scope
This page provides a step-by-step guide for creating RPC service definitions using the RpcMethodPrebuffered trait. Service definitions are shared Rust crates that define the contract between client and server, enabling compile-time type safety across platform boundaries.
For conceptual background on service definitions and their role in the architecture, see Service Definitions. For details on how method IDs are generated, see Method ID Generation. For serialization internals, see Serialization with Bitcode.
What is a Service Definition?
A service definition is a Rust struct that implements the RpcMethodPrebuffered trait. It serves as a shared contract between client and server, defining:
- Input type : The parameters passed to the RPC method
- Output type : The value returned from the RPC method
- Method ID : A unique identifier for the method (compile-time generated hash)
- Serialization logic : How to encode/decode request and response data
Service definitions are typically packaged in a separate crate that both client and server applications depend on. This ensures that any mismatch in data structures results in a compile-time error rather than a runtime failure.
Sources: README.md50 README.md:71-74
The RpcMethodPrebuffered Trait Structure
The RpcMethodPrebuffered trait defines the contract that all prebuffered service definitions must implement. This trait is automatically extended with the RpcCallPrebuffered trait, which provides the high-level call() method for client invocation.
graph TB
RpcMethodPrebuffered["RpcMethodPrebuffered\n(Core trait)"]
RpcCallPrebuffered["RpcCallPrebuffered\n(Auto-implemented)"]
UserStruct["User Service Struct\n(e.g., Add, Echo, Mult)"]
RpcMethodPrebuffered --> RpcCallPrebuffered
UserStruct -.implements.-> RpcMethodPrebuffered
UserStruct -.gets.-> RpcCallPrebuffered
RpcMethodPrebuffered --> Input["Associated Type: Input"]
RpcMethodPrebuffered --> Output["Associated Type: Output"]
RpcMethodPrebuffered --> MethodID["Constant: METHOD_ID"]
RpcMethodPrebuffered --> EncodeReq["fn encode_request()"]
RpcMethodPrebuffered --> DecodeReq["fn decode_request()"]
RpcMethodPrebuffered --> EncodeRes["fn encode_response()"]
RpcMethodPrebuffered --> DecodeRes["fn decode_response()"]
Trait Hierarchy
Sources: extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:10-21
Required Components
| Component | Type | Purpose |
|---|---|---|
Input | Associated Type | Type of the request parameters |
Output | Associated Type | Type of the response value |
METHOD_ID | u64 constant | Unique identifier for the method |
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: extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:1-6
Step-by-Step: Creating a Service Definition
Step 1: Create a Shared Crate
Create a new library crate that will be shared between client and server:
Add dependencies to Cargo.toml:
Sources: README.md71
Step 2: Define Request and Response Types
Define Rust structs for your input and output data. These must implement serde::Serialize and serde::Deserialize:
Note: The actual types can be as simple or complex as needed. They can be primitives (Vec<u8>), tuples, or complex nested structures.
Sources: README.md:146-151
Step 3: Implement the RpcMethodPrebuffered Trait
Create a struct for your service and implement the trait:
Sources: README.md:102-106 README.md146
Step 4: Use the Service Definition
Client-Side Usage
Import the service definition and call it using the RpcCallPrebuffered trait:
Sources: README.md146 extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:50-53
Server-Side Usage
Register a handler using the service definition’s METHOD_ID and decode/encode methods:
Sources: README.md:102-107 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:52-56
sequenceDiagram
participant Client as "Client Application"
participant CallTrait as "RpcCallPrebuffered::call()"
participant ServiceDef as "Service Definition\n(Add struct)"
participant Network as "Network Transport"
participant ServerEndpoint as "Server Endpoint Handler"
Note over Client,ServerEndpoint: Request Path
Client->>CallTrait: Add::call(client, vec![1.0, 2.0, 3.0])
CallTrait->>ServiceDef: encode_request(vec![1.0, 2.0, 3.0])
ServiceDef->>ServiceDef: bitcode::encode()
ServiceDef-->>CallTrait: Vec<u8> (binary data)
CallTrait->>Network: Send with METHOD_ID
Network->>ServerEndpoint: Binary frame received
ServerEndpoint->>ServiceDef: decode_request(&bytes)
ServiceDef->>ServiceDef: bitcode::decode()
ServiceDef-->>ServerEndpoint: Vec<f64>
ServerEndpoint->>ServerEndpoint: Execute: sum = 6.0
Note over Client,ServerEndpoint: Response Path
ServerEndpoint->>ServiceDef: encode_response(6.0)
ServiceDef->>ServiceDef: bitcode::encode()
ServiceDef-->>ServerEndpoint: Vec<u8> (binary data)
ServerEndpoint->>Network: Send binary response
Network->>CallTrait: Binary frame received
CallTrait->>ServiceDef: decode_response(&bytes)
ServiceDef->>ServiceDef: bitcode::decode()
ServiceDef-->>CallTrait: f64: 6.0
CallTrait-->>Client: Result: 6.0
Service Definition Data Flow
The following diagram shows how data flows through a service definition during an RPC call:
Sources: README.md:92-161 extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:50-97
Organizing Multiple Services
A service definition crate typically contains multiple service definitions. The recommended organization pattern is:
File Structure
my-service-definition/
├── Cargo.toml
└── src/
├── lib.rs
└── prebuffered/
├── mod.rs
├── add.rs
├── multiply.rs
└── echo.rs
Module Organization
src/lib.rs:
src/prebuffered/mod.rs:
This structure allows clients to import services with a clean syntax:
Sources: README.md:71-74 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs21
Service Definition Component Mapping
This diagram maps the conceptual components to their code entities:
Sources: README.md:71-119 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:21-68
Large Payload Handling
Service definitions automatically handle large payloads through a smart transport strategy. When encoded request parameters exceed DEFAULT_SERVICE_MAX_CHUNK_SIZE, they are automatically sent as a chunked payload rather than inline in the request header.
Transport Strategy Selection
| Encoded Size | Transport Method | Field Used |
|---|---|---|
< DEFAULT_SERVICE_MAX_CHUNK_SIZE | Inline in header | rpc_param_bytes |
≥ DEFAULT_SERVICE_MAX_CHUNK_SIZE | Chunked payload | rpc_prebuffered_payload_bytes |
This strategy is implemented automatically by the RpcCallPrebuffered trait and requires no special handling in service definitions:
Sources: extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:30-48 extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:58-65
Large Payload Test Example
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:295-311
Best Practices
Type Design
| Practice | Rationale |
|---|---|
Use #[derive(Serialize, Deserialize)] | Required for bitcode serialization |
Add #[derive(Debug, Clone)] | Helpful for testing and debugging |
| Keep types simple | Simpler types serialize more efficiently |
Use Vec<u8> for binary data | Avoids double-encoding overhead |
Method Naming
| Practice | Example | Rationale |
|---|---|---|
| Use PascalCase for struct names | Add, GetUserProfile | Rust convention for type names |
| Use descriptive names | CalculateSum vs Calc | Improves code readability |
| Match domain concepts | AuthenticateUser | Makes intent clear |
Error Handling
Service definitions should use io::Error for encoding/decoding failures:
This ensures consistent error propagation through the RPC framework.
Sources: extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:6-7
Versioning
When evolving service definitions:
- Additive changes are safe : Adding new optional fields to request/response types
- Breaking changes require new methods : Changing input/output types requires a new
METHOD_ID - Maintain backwards compatibility : Keep old service definitions until all clients migrate
Sources: README.md50
Complete Example: Echo Service
Here is a complete example of a simple Echo service definition:
Usage:
Sources: README.md:114-118 README.md:150-151 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:64-67 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:131-132