Lesson 18 of 32 intermediate 5 min read

Before this:What is RPC?Data formats: JSON and friends

gRPC & Protocol Buffers

Key takeaways gRPC is today’s mainstream RPC framework: you write your messages and services once in a .proto file, a compiler generates typed client and server code in your languages, and calls travel as compact Protocol Buffers binary over HTTP/2. Protobuf fields carry numbers, not names, on the wire — which makes messages small and makes the numbers sacred for compatibility. gRPC’s standout feature is streaming: server-, client-, and bidirectional streams are first-class call types, which is why it fits live audio so naturally.

The previous lesson gave you RPC the idea; this one gives you RPC as actually practiced. gRPC (from Google, now open) and its wire format Protocol Buffers are the pairing you’ll meet everywhere from microservices to — in Unit 6 — a scanner daemon streaming call audio.

The .proto file: one contract, written once

Everything starts from an interface definition. Here’s a miniature of a scanner-flavoured service:

syntax = "proto3";

package scanner.v1;

message CallRequest {
  uint32 call_id = 1;
}

message AudioChunk {
  uint32 call_id   = 1;
  bytes  pcm       = 2;  // raw audio samples
  uint64 timestamp = 3;
}

service AudioService {
  // One request in, a stream of chunks back.
  rpc StreamAudio(CallRequest) returns (stream AudioChunk);
}

Read it as a contract in the Unit 1 sense, made machine-checkable: messages are typed structures, services are sets of rpc methods with request and response types. The = 1, = 2 are field numbers, and they’re the heart of protobuf — more on them in a moment.

From this file, protoc (the protobuf compiler) generates code: Go structs and interfaces, Python classes, TypeScript, whatever you target. The generated stub is the one from last lesson’s diagram, now concrete: your Go client gets a method with the exact signature the .proto promised, context-first for deadlines and cancellation.

Protobuf on the wire: numbers, not names

JSON spells out "talkgroup" in every message. Protobuf writes each field as its number plus a compactly-encoded value — talkgroup: 1201 costs about three bytes. The consequences cut both ways:

  • Small and fast. No field-name bytes, no text-to-number parsing; encoding and decoding are near-memcpy for much of a message.
  • Opaque. The bytes are unreadable without the schema — the text-vs-binary trade in its purest form.
  • The numbers are the contract. Renaming a field is free (names never hit the wire!), but reusing or changing a field number is the protobuf cardinal sin — old messages would silently decode into the wrong field: the worst-case “meaning change” from the contracts lesson. Deleted fields’ numbers are retired forever (protobuf even has a reserved keyword for it). Unknown fields are skipped by decoders, so adding fields is safe — protobuf bakes the additive-evolution discipline into the format itself.

Four call shapes, streaming built in

Where classic RPC had only request/response, gRPC methods come in four shapes:

Shape Signature sketch Example
Unary request → response Fetch one talkgroup’s details
Server streaming request → stream of responses StreamAudio above: subscribe once, chunks flow until the call ends
Client streaming stream of requests → response Upload samples, get one analysis back
Bidirectional stream ↔ stream Live session: commands up, telemetry down, interleaved

Streams ride HTTP/2 framing underneath, with flow control — the transport-level half of the backpressure story — plus per-call deadlines, cancellation, and a standard status code vocabulary. This is the “sharp edges made explicit” promise kept: the failure modes RPC hides are surfaced as API you’re forced to touch.

When gRPC, when REST?

gRPC shines where both ends are programs you (or your ecosystem) control: service-to-service traffic, streaming, tight typed contracts, performance- sensitive paths. Its friction is at the edges: browsers can’t speak native gRPC (proxies like gRPC-Web exist), and you lose curl-ability — debugging means grpcurl and schema-aware tooling rather than eyeballs on text. Hence the by-now-familiar split, which GopherTrunk itself follows: REST for records and public surface, gRPC for the audio stream where binary framing and server streaming pay their way.

Rule of thumb: gRPC between services, REST at the edge — and never change a protobuf field number.

Quick check: which change to a stable .proto message is safe?

Recap

  • The .proto file defines messages and services once; generated stubs give every language a typed client and server — the schema-first workflow.
  • Protobuf encodes field numbers, not names: compact and fast, opaque without the schema.
  • Field numbers are sacred — rename freely, never renumber or reuse; adding fields is safe by design.
  • gRPC offers unary, server-, client-, and bidirectional streaming calls with deadlines, cancellation, and flow control built in.
  • Use gRPC between services and for streams, REST at the public edge — the split real systems, GopherTrunk included, actually practice.

Next up: Text vs binary protocols.