RPC & gRPC
REST asks you to think in nouns and verbs. RPC — Remote Procedure Call — asks you to think in
functions. The goal is older and more direct than REST: make calling code on another machine look
and feel as much as possible like calling a function in your own process. You write user := GetUser(42) and somewhere underneath, that turns into bytes on a wire, a call on a remote server,
and a value coming back — and ideally you barely notice.
The RPC model: hide the network behind a function call
Section titled “The RPC model: hide the network behind a function call”The mechanism is a stub (or client) generated for you. You call a normal-looking method; the stub serializes the arguments, sends them, waits, and deserializes the reply.
YOUR CODE THE WIRE REMOTE SERVICE ───────── ──────── ────────────── GetUser(42) ──► [client stub serializes] ──► [server stub deserializes] ──► GetUser(42) user ◄──────── [client stub deserializes] ◄── [server stub serializes] ◄──── return userThis is wonderfully ergonomic. It is also the source of RPC’s oldest danger: by hiding the network, it tempts you to forget the network is there — that the call can be slow, can fail, can be retried. The fallacies of distributed computing are essentially a list of the lies a too-transparent RPC abstraction encourages you to believe.
gRPC: the modern, fast incarnation
Section titled “gRPC: the modern, fast incarnation”The dominant RPC framework today is gRPC. It is worth understanding as three decisions stacked together, because each one is a deliberate trade.
1. Protocol Buffers (protobuf): a schema and a binary format
Section titled “1. Protocol Buffers (protobuf): a schema and a binary format”You define your service and its messages in a .proto file — a strict, typed contract:
message GetUserRequest { int32 id = 1; }message User { int32 id = 1; string name = 2; string email = 3; }
service UserService { rpc GetUser (GetUserRequest) returns (User);}From this single file, gRPC generates client and server code in many languages. The contract
lives in a file the compiler enforces — not in prose documentation that drifts. On the wire, protobuf
is binary, identifying fields by number (the = 1, = 2) rather than by repeating field names as
JSON does. The result is dramatically smaller and faster to parse than JSON text.
What does this buy us, and what does it cost? It buys type safety across the network and
compact, fast payloads. It costs human readability — you can’t curl an endpoint and eyeball
the response; you need tooling to decode binary protobuf — and it adds a build step (code
generation) to your pipeline.
2. HTTP/2 as the transport
Section titled “2. HTTP/2 as the transport”gRPC runs over HTTP/2, which gives it two things REST-over-HTTP/1.1 historically lacked: multiplexing (many concurrent calls share one connection without head-of-line blocking) and native streaming.
3. Streaming in four shapes
Section titled “3. Streaming in four shapes”Because of HTTP/2, a gRPC method isn’t limited to one-request-one-response. It supports:
Unary: client → 1 request ........ server → 1 responseServer streaming: client → 1 request ........ server → many responses (a stream)Client streaming: client → many ........ server → 1 responseBidirectional: client ⇄ server, both stream simultaneouslyThis makes gRPC a natural fit for things like live feeds, telemetry, and chat backends between services — workloads that are awkward to express in plain REST.
When RPC beats REST
Section titled “When RPC beats REST”RPC, and gRPC specifically, shines in a recognizable setting: internal, service-to-service communication where you control both ends.
- Low latency, high volume. Binary protobuf over multiplexed HTTP/2 is materially faster and lighter than JSON over HTTP/1.1. When two services chat thousands of times a second, that difference compounds. (Pair this intuition with latency, throughput & the numbers.)
- Strong typing and codegen. The
.protois a single source of truth; a breaking change shows up as a compile error in the client, not a 2 a.m. production surprise. - Streaming. First-class bidirectional streams without bolting on a second protocol.
This is why gRPC is the common backbone behind the public edge: a REST or GraphQL front door for the outside world, gRPC between your microservices internally.
What RPC costs
Section titled “What RPC costs”The ergonomics are not free, and the costs are the mirror image of REST’s strengths.
- Coupling. Client and server share a generated contract and often a build pipeline. They evolve together; an external partner can’t just point a browser at it. RPC trades REST’s loose, universal contract for a tight, efficient one.
- Tooling and ecosystem friction. Browsers can’t speak raw gRPC directly (gRPC-Web exists as a proxied workaround). Debugging binary traffic needs special tools. Caching, which REST gets from HTTP almost for free, must be built deliberately.
- The transparency trap. Because a remote call looks like a local one, teams forget to design for partial failure, timeouts, and retries. A local function never times out; an RPC always can.
The thread
Section titled “The thread”How do two services you control talk as cheaply and safely as possible? RPC answers: pretend the network isn’t there — generate typed stubs from a shared contract and let a function call carry the weight. gRPC sharpens this with binary protobuf and HTTP/2 streaming, buying speed and type safety at the cost of tight coupling, harder tooling, and the ever-present temptation to forget that the network can still fail.
Check your understanding
Section titled “Check your understanding”- What problem is RPC fundamentally trying to solve, and how does a generated stub achieve it?
- Name the three stacked decisions inside gRPC (schema/format, transport, call shapes) and what each one buys.
- Why is binary protobuf both faster than JSON and harder to debug?
- Give two concrete reasons internal microservices often prefer gRPC while the public edge stays REST or GraphQL.
- Explain “the transparency trap” — why does making a remote call look local lead to bugs?