Skip to content

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 user

This 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.

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.

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.

Because of HTTP/2, a gRPC method isn’t limited to one-request-one-response. It supports:

Unary: client → 1 request ........ server → 1 response
Server streaming: client → 1 request ........ server → many responses (a stream)
Client streaming: client → many ........ server → 1 response
Bidirectional: client ⇄ server, both stream simultaneously

This 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.

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 .proto is 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.

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.

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.

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 curl binary 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.
  1. What problem is RPC fundamentally trying to solve, and how does a generated stub achieve it?
  2. Name the three stacked decisions inside gRPC (schema/format, transport, call shapes) and what each one buys.
  3. Why is binary protobuf both faster than JSON and harder to debug?
  4. Give two concrete reasons internal microservices often prefer gRPC while the public edge stays REST or GraphQL.
  5. Explain “the transparency trap” — why does making a remote call look local lead to bugs?
Show answers
  1. 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.
  2. 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.
  3. 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’t curl and eyeball it — you need tooling to decode the binary.
  4. (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 .proto is 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.
  5. 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.