Home > Engineering > Infrastructure > bare-for-pear > avsc-rpc > Transports
avsc-rpc Transports
How RPC messages move between client and server. The transport is a deployment concern — the schema contract is invariant.
Transport Model
avsc-rpc separates the protocol boundary (schema contract, handshake, message encoding) from the transport (how bytes move). Handler code is transport-agnostic. The same service definition works across all transports.
Two categories:
- Stateful — persistent connection, single handshake, multiplexed messages. TCP, in-memory.
- Stateless — handshake per request, one message per connection. HTTP.
In-Memory
Direct object passing between client and server in the same process. No serialization, no framing.
const client = service.createClient({ server })
Or explicitly:
const client = service.createClient()
client.createChannel(server.createChannel, {
objectMode: true
})
Used for testing and subject-internal communication. The RPC boundary is enforced — schema validation happens — but there is no wire encoding.
TCP (Stateful)
Persistent connection with a single handshake and multiplexed messages. The natural transport for inter-process communication.
const net = require('bare-net')
// Server
const tcpServer = net.createServer((socket) => {
server.createChannel(socket)
})
tcpServer.listen(8090)
// Client
const socket = net.connect(8090)
client.createChannel(socket)
The socket is both readable and writable — avsc-rpc uses it as a duplex transport. Messages are multiplexed with ID headers so responses match requests.
Reconnection
TCP channels do not auto-reconnect. When a connection
drops, the channel emits 'eot' and is destroyed.
Create a new channel on a new socket.
function connect () {
const socket = net.connect(8090)
const channel = client.createChannel(socket)
channel.on('eot', () => {
// Reconnect after delay
setTimeout(connect, 1000)
})
}
HTTP (Stateless)
Request-response pattern. Each message is a complete interaction — handshake, request, response.
client.createChannel((cb) => {
// cb(err, readable) when response arrives
const req = http.request(opts, (res) => cb(null, res))
cb(null, req) // return writable for request
})
The factory function is called for each message. Suitable for HTTP servers, serverless functions, or any request-response protocol.
Channel Options
| Option | Default | Description |
|---|---|---|
objectMode |
false |
true: object passing. false: binary framing |
noPing |
false |
Skip initial handshake ping |
timeout |
10000 |
Per-channel timeout in ms |
endWritable |
true |
End writable after stateless request |
scope |
— | Message ID scoping |
serverHash |
— | Pre-populate protocol adapter |
objectMode
When true, messages pass as JavaScript objects —
no serialization. Used for in-memory transports.
When false (default), messages are serialized using
Avro binary encoding with wire framing. Two framing
formats are available (see
Wire Protocol):
- Standard (FrameEncoder/FrameDecoder) — Avro specification framing
- Netty (NettyEncoder/NettyDecoder) — Java Netty-compatible framing, used by default for stateful binary channels
Transport Summary
| Transport | Type | Framing | Multiplexing | Use Case |
|---|---|---|---|---|
| In-memory | Stateful | None (objects) | Yes | Testing, IPC |
| TCP | Stateful | Netty | Yes | Inter-process |
| HTTP | Stateless | Frame | No | Request-response |
| Custom | Either | Configurable | Configurable | Special protocols |
Custom Transports
Any duplex stream (or pair of readable/writable streams) can serve as a transport:
// Duplex stream
client.createChannel(duplexStream)
// Separate readable/writable
client.createChannel({
readable: inputStream,
writable: outputStream
})
The transport only needs to deliver bytes reliably. avsc-rpc handles framing, handshake, multiplexing, and timeout.