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.
The architect’s lens
Section titled “The architect’s lens”gRPC is the internal backbone of choice, but its costs mirror its strengths — run it through the five questions:
- Why does it exist? Because REST makes you think in nouns and verbs, yet services you control often just want to call a function on another machine. RPC makes a remote call look local via a generated stub that serializes args, sends them, and deserializes the reply.
- What problem does it solve? Cheap, safe, typed service-to-service calls. gRPC stacks protobuf (a compiler-enforced contract plus compact binary), HTTP/2 (multiplexing with no head-of-line blocking), and four streaming shapes — materially faster and lighter than JSON-over-HTTP/1.1 for chatty internal traffic.
- What are the trade-offs? You buy type safety and speed at the cost of human readability (you can’t
curlbinary protobuf), a codegen build step, and tight coupling — client and server share a generated contract and evolve together. Caching, free in REST, must be built deliberately. - When should I avoid it? At the public edge — browsers and external partners can’t speak raw gRPC (gRPC-Web is a proxied workaround), so keep REST/GraphQL as the front door and gRPC between internal microservices. Avoid it where a loose, universal, cacheable contract matters more than raw speed.
- What breaks if I forget it’s remote? The transparency trap: because the call looks local, teams forget partial failure, timeouts, and retries — a local function never times out, an RPC always can. And a slow call hides inside an RPC fan-out unless you add distributed tracing.
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?
Show answers
- RPC tries to make calling code on another machine feel like calling a local function — you write
GetUser(42)and barely notice the network. A generated stub achieves it: you call a normal-looking method, and the stub serializes the arguments, sends them, waits, and deserializes the reply for you. - Protocol Buffers (schema + binary format) buys type safety across the network and compact, fast payloads; HTTP/2 (transport) buys multiplexing without head-of-line blocking and native streaming; the four call shapes (unary, server-, client-, and bidirectional streaming) buy first-class streaming without bolting on a second protocol.
- Protobuf is faster because it’s binary and identifies fields by number (
= 1,= 2) instead of repeating field names as JSON text, so it’s smaller and quicker to parse. It’s harder to debug for the same reason: you can’tcurland eyeball it — you need tooling to decode the binary. - (1) Low latency at high volume — binary protobuf over multiplexed HTTP/2 is materially faster and lighter than JSON over HTTP/1.1, which compounds when services chat thousands of times a second. (2) Strong typing and codegen — the
.protois a single source of truth, so a breaking change surfaces as a compile error, not a 2 a.m. production surprise. The public edge stays REST/GraphQL because browsers and external partners can’t easily speak raw gRPC. - The transparency trap is that because a remote call looks local, teams forget to design for partial failure, timeouts, and retries. A local function never times out; an RPC always can — so the very abstraction that makes RPC ergonomic encourages believing the fallacies of distributed computing.