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

Loading…

Creating Service Definitions

Relevant source files

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

ComponentTypePurpose
InputAssociated TypeType of the request parameters
OutputAssociated TypeType of the response value
METHOD_IDu64 constantUnique identifier for the method
encode_request()FunctionSerializes Input to Vec<u8>
decode_request()FunctionDeserializes Vec<u8> to Input
encode_response()FunctionSerializes Output to Vec<u8>
decode_response()FunctionDeserializes 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 SizeTransport MethodField Used
< DEFAULT_SERVICE_MAX_CHUNK_SIZEInline in headerrpc_param_bytes
DEFAULT_SERVICE_MAX_CHUNK_SIZEChunked payloadrpc_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

PracticeRationale
Use #[derive(Serialize, Deserialize)]Required for bitcode serialization
Add #[derive(Debug, Clone)]Helpful for testing and debugging
Keep types simpleSimpler types serialize more efficiently
Use Vec<u8> for binary dataAvoids double-encoding overhead

Method Naming

PracticeExampleRationale
Use PascalCase for struct namesAdd, GetUserProfileRust convention for type names
Use descriptive namesCalculateSum vs CalcImproves code readability
Match domain conceptsAuthenticateUserMakes 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:

  1. Additive changes are safe : Adding new optional fields to request/response types
  2. Breaking changes require new methods : Changing input/output types requires a new METHOD_ID
  3. 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