Design and Refactoring of ByteDance's Node.js RPC Framework
This article explains the motivation, design principles, model architecture, and implementation challenges of rebuilding ByteDance's Node.js RPC system, covering DDD‑based decomposition, protocol and connection abstractions, multi‑protocol nesting, client/server creation APIs, and performance‑optimized context extensions.
Background – ByteDance's Web Infra team maintains various Node.js infrastructure components, including service discovery, governance, RPC, databases, and messaging. In early 2021 they decided to refactor their RPC implementation to keep up with business growth.
What is RPC? – Remote Procedure Call is a generic network call mechanism used widely in backend services (e.g., Dubbo, Thrift, gRPC, REST). Understanding RPC is essential for full‑stack and backend Node.js developers.
Current Situation & Requirements – ByteDance uses multiple serialization and network protocols, so a custom RPC solution is needed. Desired features include support for various serialization formats (Thrift, Protobuf, JSON), multiple network protocols (TCP, HTTP, HTTP/2), and maximal reuse of existing code.
Design RPC
DDD (Domain‑Driven Design)
The design starts with DDD to align code structure with business domain concepts.
Decompose RPC
RPC is broken down into two fundamental layers: a network protocol (the transmission medium) and a serialization protocol (the data language).
Model Construction
Four core interfaces are defined:
interface Connection { read(): Promise
; write(buf: Buffer): Promise
; } interface Protocol { encode(): Promise
; decode(): Promise
; } interface Handle { execute(): Promise
; }A Handle abstracts the actual RPC invocation method, allowing combinations such as TCP + Thrift, HTTP + Protobuf, etc.
To decouple connection creation from usage, a ConnectionProvider is introduced, and similarly a ProtocolProvider for serialization.
Model Overview
Connection – focuses on network I/O only.
Protocol – focuses on serialization/computation only.
Handle – describes how an RPC call is executed.
ConnectionProvider – creates/recycles connections, handling service discovery, mesh, pooling.
ProtocolProvider – creates serialization protocols.
Context – carries request/response and metadata.
ConfigCenter – remote configuration.
Middleware – extensibility.
Problems Encountered
Client & Server Creation
Because many models are involved, creating clients/servers can be complex. Two APIs are offered: a high‑level createClient()/createServer() that integrates logging, metrics, tracing, etc., and a low‑level API for custom needs.
Multi‑Protocol Nesting
Real‑world usage requires combining header and payload protocols (e.g., Binary Thrift, TTHeader + Binary Thrift). The solution splits Protocol into HeaderProtocol and PayloadProtocol and uses dynamic buffers with connection.cork / connection.uncork to compose them.
class DynamicBuffer {}
connection.cork(new DynamicBuffer());
await payloadProtocol.encode(ctx);
let payload = connection.uncork();
for (let i = headerProtocols.length - 1; i >= 1; i--) {
const headerProtocol = headerProtocols[i];
connection.cork(new DynamicBuffer());
await headerProtocol.encode(ctx, payload);
payload = connection.uncork();
}
await headerProtocols[0].encode(ctx, payload);Context Extension Performance
Extending the Context with many Object.defineProperty calls caused significant overhead. Inspired by fastify.decorate() , the team modifies the Context prototype directly, achieving near‑zero cost for dynamic properties.
const ctx = { tags: { log_id: '' } };
Object.defineProperty(ctx, 'logId', {
get() { return ctx.tags.log_id; },
set(logId) { ctx.tags.log_id = logId; }
});Summary – The article details the end‑to‑end design of a modular, extensible RPC framework for Node.js, covering decomposition, model definitions, multi‑protocol composition, API ergonomics, and performance‑aware context handling. Future work includes adding JSON, Protobuf, HTTP/2 support and possibly exposing the RPC stack to browsers.
ByteDance Web Infra
ByteDance Web Infra team, focused on delivering excellent technical solutions, building an open tech ecosystem, and advancing front-end technology within the company and the industry | The best way to predict the future is to create it
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.