This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Layered Architecture
Relevant source files
Purpose and Scope
This page documents the separation of concerns in rust-muxio's three-layer architecture: the core multiplexing layer, the RPC abstraction layer, and the transport implementation layer. Each layer has distinct responsibilities and well-defined interfaces, enabling modularity, testability, and platform independence.
For information about the specific binary protocol used in the core layer, see Binary Framing Protocol. For details on how to create service definitions in the RPC layer, see Creating Service Definitions. For information about specific transport implementations, see Transport Implementations.
Architectural Overview
The rust-muxio system is structured as three independent layers, each building upon the previous one without creating tight coupling. This design allows developers to use only the layers they need and to implement custom components at any level.
Sources: Cargo.toml:19-31 README.md:16-23 extensions/README.md
graph TB
subgraph TransportLayer["Transport Layer (Platform-Specific)"]
TokioServer["muxio-tokio-rpc-server\nRpcServer struct"]
TokioClient["muxio-tokio-rpc-client\nRpcClient struct"]
WasmClient["muxio-wasm-rpc-client\nRpcWasmClient struct"]
end
subgraph RpcLayer["RPC Abstraction Layer (Transport-Agnostic)"]
RpcService["muxio-rpc-service\nRpcMethodPrebuffered trait"]
CallerInterface["muxio-rpc-service-caller\nRpcServiceCallerInterface trait"]
EndpointInterface["muxio-rpc-service-endpoint\nRpcServiceEndpointInterface trait"]
end
subgraph CoreLayer["Core Multiplexing Layer (Runtime-Agnostic)"]
RpcDispatcher["muxio/src/rpc_dispatcher.rs\nRpcDispatcher struct"]
RpcRequest["muxio/src/rpc_request_response.rs\nRpcRequest/RpcResponse"]
BinaryFraming["muxio/src/rpc_dispatcher.rs\nBinary framing protocol"]
end
TokioServer --> CallerInterface
TokioServer --> EndpointInterface
TokioClient --> CallerInterface
WasmClient --> CallerInterface
CallerInterface --> RpcService
EndpointInterface --> RpcService
RpcService --> RpcDispatcher
CallerInterface --> RpcDispatcher
EndpointInterface --> RpcDispatcher
RpcDispatcher --> RpcRequest
RpcDispatcher --> BinaryFraming
Layer 1: Core Multiplexing Layer
The core layer, contained entirely within the muxio crate, provides transport-agnostic and runtime-agnostic stream multiplexing. This layer has zero knowledge of RPC semantics, serialization formats, or network transports.
Core Components
| Component | File Path | Responsibility |
|---|---|---|
RpcDispatcher | muxio/src/rpc_dispatcher.rs | Manages concurrent request/response correlation and frame routing |
RpcRequest | muxio/src/rpc_request_response.rs | Defines request structure with method ID, params, and payload |
RpcResponse | muxio/src/rpc_request_response.rs | Defines response structure with result or error |
RpcHeader | muxio/src/rpc_request_response.rs | Wraps requests/responses with metadata |
| Binary framing | muxio/src/rpc_dispatcher.rs | Low-level protocol for chunking and reassembling byte streams |
Key Characteristics
The core layer operates exclusively on raw bytes (Vec<u8>). It provides callbacks for receiving data and functions for sending data, but never interprets the semantic meaning of the data. The RpcDispatcher handles:
- Assigning unique request IDs for correlation
- Multiplexing multiple concurrent requests over a single connection
- Routing incoming responses to the correct waiting caller
- Fragmenting large payloads into transmission frames
- Reassembling frames into complete messages
Sources: muxio/src/rpc_dispatcher.rs muxio/src/rpc_request_response.rs README.md:28-29
Layer 2: RPC Abstraction Layer
The RPC layer provides type-safe abstractions for defining and invoking remote procedures without dictating transport implementation. This layer consists of three cooperating crates.
graph TB
subgraph ServiceDefinition["muxio-rpc-service"]
RpcMethodPrebuffered["RpcMethodPrebuffered trait\nMETHOD_ID: u64\nencode_request()\ndecode_request()\nencode_response()\ndecode_response()"]
end
subgraph CallerSide["muxio-rpc-service-caller"]
CallerInterface["RpcServiceCallerInterface trait\ncall_prebuffered()\nget_dispatcher()"]
CallImpl["RpcCallPrebuffered trait\ncall() - default implementation"]
end
subgraph EndpointSide["muxio-rpc-service-endpoint"]
EndpointInterface["RpcServiceEndpointInterface trait\nregister_prebuffered()\ndispatch_request()"]
HandlerRegistry["Method ID → Handler mapping"]
end
RpcMethodPrebuffered --> CallImpl
RpcMethodPrebuffered --> HandlerRegistry
CallerInterface --> CallImpl
EndpointInterface --> HandlerRegistry
RPC Layer Components
Trait Responsibilities
RpcMethodPrebuffered (defined in extensions/muxio-rpc-service)
This trait defines the contract for a single RPC method. Each implementation provides:
- A compile-time constant
METHOD_IDgenerated from the method name - Encoding/decoding functions for request parameters
- Encoding/decoding functions for response data
RpcServiceCallerInterface (defined in extensions/muxio-rpc-service-caller)
This trait abstracts the client-side capability to invoke RPC methods. Any type implementing this trait can:
- Send prebuffered requests via
call_prebuffered() - Access the underlying
RpcDispatcherfor low-level operations - Be used interchangeably across native and WASM clients
RpcServiceEndpointInterface (defined in extensions/muxio-rpc-service-endpoint)
This trait abstracts the server-side capability to handle RPC requests. Any type implementing this trait can:
- Register handler functions via
register_prebuffered() - Route incoming requests to appropriate handlers based on
METHOD_ID - Execute handlers and return responses through the dispatcher
Separation of Concerns
The RPC layer enforces a clean separation:
| Concern | Responsible Component |
|---|---|
| Method signature and data format | RpcMethodPrebuffered implementation |
| Client invocation mechanics | RpcServiceCallerInterface implementation |
| Server dispatch mechanics | RpcServiceEndpointInterface implementation |
| Request correlation and multiplexing | Core layer RpcDispatcher |
| Network transmission | Transport layer implementations |
Sources: extensions/muxio-rpc-service extensions/muxio-rpc-service-caller extensions/muxio-rpc-service-endpoint README.md:46-49
Layer 3: Transport Implementation Layer
The transport layer provides concrete implementations of the RPC abstraction layer interfaces for specific runtime environments and network transports. Each implementation handles platform-specific concerns like connection management, state tracking, and async runtime integration.
graph TB
subgraph TokioServerImpl["muxio-tokio-rpc-server"]
RpcServer["RpcServer struct\nserve_with_listener()\nendpoint() → RpcEndpoint"]
RpcEndpoint["RpcEndpoint struct\nimplements RpcServiceEndpointInterface\nregister_prebuffered()"]
AxumWs["Axum WebSocket handler\ntokio-tungstenite integration"]
end
subgraph TokioClientImpl["muxio-tokio-rpc-client"]
RpcClient["RpcClient struct\nimplements RpcServiceCallerInterface\nnew(host, port)\nset_state_change_handler()"]
ClientWs["tokio-tungstenite WebSocket\nConnection management"]
end
subgraph WasmClientImpl["muxio-wasm-rpc-client"]
RpcWasmClient["RpcWasmClient struct\nimplements RpcServiceCallerInterface\nnew(url)\nwasm-bindgen bridge"]
BrowserWs["JavaScript WebSocket API\nvia wasm-bindgen"]
end
RpcServer --> RpcEndpoint
RpcServer --> AxumWs
RpcClient --> ClientWs
RpcWasmClient --> BrowserWs
Transport Implementations
Implementation Comparison
| Feature | muxio-tokio-rpc-server | muxio-tokio-rpc-client | muxio-wasm-rpc-client |
|---|---|---|---|
| Runtime | Tokio async | Tokio async | Browser event loop |
| Transport | Axum + tokio-tungstenite | tokio-tungstenite | JavaScript WebSocket |
| Interface | RpcServiceEndpointInterface | RpcServiceCallerInterface | RpcServiceCallerInterface |
| State tracking | Built-in | RpcTransportState enum | RpcTransportState enum |
| Platform | Native (server-side) | Native (client-side) | WebAssembly (browser) |
Transport Layer Responsibilities
Each transport implementation handles:
- Connection Lifecycle : Establishing, maintaining, and closing connections
- State Management : Tracking connection state and notifying callbacks via
set_state_change_handler() - Byte Transport : Reading from and writing to the underlying socket
- Dispatcher Integration : Creating an
RpcDispatcherand wiring its callbacks to network I/O - Error Propagation : Translating transport errors to RPC errors
Sources: extensions/muxio-tokio-rpc-server extensions/muxio-tokio-rpc-client extensions/muxio-wasm-rpc-client README.md:36-40
Layer Interaction and Data Flow
The following diagram traces how a single RPC call flows through all three layers, from application code down to the network and back:
sequenceDiagram
participant App as "Application Code"
participant Method as "RpcMethodPrebuffered\n(Layer 2: RPC)"
participant Caller as "RpcServiceCallerInterface\n(Layer 2: RPC)"
participant Dispatcher as "RpcDispatcher\n(Layer 1: Core)"
participant Transport as "Transport Implementation\n(Layer 3)"
participant Network as "WebSocket\nConnection"
App->>Method: Add::call(rpc_client, [1.0, 2.0, 3.0])
Method->>Method: encode_request() → Vec<u8>
Method->>Caller: call_prebuffered(METHOD_ID, request_bytes)
Caller->>Dispatcher: dispatch_request(REQUEST_ID, METHOD_ID, bytes)
Dispatcher->>Dispatcher: Store pending request with REQUEST_ID
Dispatcher->>Dispatcher: Serialize to binary frames
Dispatcher->>Transport: send_bytes_callback(frame_bytes)
Transport->>Network: Write binary data
Network->>Transport: Receive binary data
Transport->>Dispatcher: receive_bytes(frame_bytes)
Dispatcher->>Dispatcher: Reassemble frames
Dispatcher->>Dispatcher: Match REQUEST_ID to pending request
Dispatcher->>Caller: Response ready
Caller->>Method: decode_response(response_bytes)
Method->>App: Return typed result: 6.0
Layer Boundaries
The boundaries between layers are enforced through well-defined interfaces:
| Boundary | Interface | Direction |
|---|---|---|
| Application → RPC | RpcMethodPrebuffered::call() | Typed parameters → Vec<u8> |
| RPC → Core | call_prebuffered() on RpcServiceCallerInterface | Vec<u8> + METHOD_ID → Request correlation |
| Core → Transport | Callbacks (send_bytes_callback, receive_bytes) | Binary frames ↔ Network I/O |
| Transport → Network | Platform-specific APIs | Raw socket operations |
Sources: extensions/muxio-rpc-service-caller/src/caller_interface.rs extensions/muxio-rpc-service-endpoint/src/endpoint_interface.rs muxio/src/rpc_dispatcher.rs
Benefits of Layered Separation
Modularity
Each layer can be developed, tested, and evolved independently. Changes to the binary framing protocol in Layer 1 do not require modifications to Layer 2 or Layer 3 code, as long as the callback interface remains stable.
Testability
Layers can be tested in isolation:
- Core layer : Unit tests with mock callbacks can verify frame reassembly without network I/O
- RPC layer : Integration tests can use in-memory transports to verify method dispatch
- Transport layer : Integration tests can verify connection management against real servers
graph TB
SharedDef["example-muxio-rpc-service-definition\nAdd, Mult, Echo methods\nRpcMethodPrebuffered implementations"]
SharedDef --> TokioClient["muxio-tokio-rpc-client\nNative Tokio runtime"]
SharedDef --> WasmClient["muxio-wasm-rpc-client\nBrowser WebAssembly"]
SharedDef --> TokioServer["muxio-tokio-rpc-server\nServer endpoint handlers"]
TokioClient --> NativeApp["Native Application"]
WasmClient --> BrowserApp["Browser Application"]
TokioServer --> ServerApp["Server Application"]
Platform Independence
The same service definition can be used across all platforms because Layers 1 and 2 have no platform-specific dependencies:
Extensibility
New transport implementations can be added without modifying existing code:
- Implement
RpcServiceCallerInterfacefor client-side transports - Implement
RpcServiceEndpointInterfacefor server-side transports - Use the same service definitions and core dispatcher logic
Examples of potential future transports:
- HTTP/2 with binary frames
- Unix domain sockets
- Named pipes
- In-process channels for testing
Sources: README.md:34-35 README.md:42-52 extensions/README.md
Code Organization by Layer
The workspace structure directly reflects the layered architecture:
| Layer | Crates | Location |
|---|---|---|
| Core (Layer 1) | muxio | Root directory |
| RPC Abstraction (Layer 2) | muxio-rpc-service | |
muxio-rpc-service-caller | ||
muxio-rpc-service-endpoint | extensions/ | |
| Transport (Layer 3) | muxio-tokio-rpc-server | |
muxio-tokio-rpc-client | ||
muxio-wasm-rpc-client | extensions/ | |
| Service Definitions | example-muxio-rpc-service-definition | examples/ |
| Testing Utilities | muxio-ext-test | extensions/ |
Dependency Graph
This dependency structure ensures that:
- The core has no dependencies on higher layers
- The RPC abstraction layer has no knowledge of transport implementations
- Transport implementations depend on both core and RPC layers
- Service definitions depend only on
muxio-rpc-service
Sources: Cargo.toml:19-31 Cargo.toml:40-47
Dismiss
Refresh this wiki
Enter email to refresh