Free — no signup required

gRPC and Protocol Buffers: Binary Contracts

3 min read

Defining the Contract in .proto Files

The foundation of gRPC is the Protocol Buffer (Protobuf) schema — a .proto file that defines the data structures (called messages) and the service interface (the available RPC methods). This file is the single source of truth shared between client and server.

// sensor.proto
syntax = "proto3";

package sensor;

// The data structure for a sensor reading
message SensorReading {
  float temperature = 1;   // field number 1
  int32 sensor_id = 2;     // field number 2
  int64 timestamp = 3;     // field number 3
}

// The data structure for the server's response
message RecordResponse {
  bool success = 1;
  string message = 2;
}

// The service definition — what RPCs are available
service SensorService {
  rpc RecordReading (SensorReading) returns (RecordResponse);
  rpc StreamReadings (stream SensorReading) returns (RecordResponse);
}

The = 1, = 2, = 3 are field numbers — unique identifiers for each field in the binary encoding. In the serialized binary, field names are never transmitted. Instead, each value is prefixed with its field number and wire type (a few bits indicating the data type). The field temperature = 22.5 becomes roughly 5 bytes: 1 byte for the field tag (field number 1, wire type float) and 4 bytes for the IEEE 754 float value. Compare to JSON's "temperature":22.5 which is 18 bytes.

The protoc compiler takes this .proto file and generates client and server code in your target language (Go, Python, Java, C++, Node.js, and many more). The generated code handles all serialization and deserialization automatically.

The Four gRPC Communication Patterns

Because gRPC is built on HTTP/2, it natively supports streaming in both directions. This gives it four distinct communication patterns:

Pattern Client sends Server sends Use case
Unary Single request Single response Standard API call (fetch user by ID)
Server Streaming Single request Stream of responses Live stock prices, log tailing
Client Streaming Stream of requests Single response Uploading a large file in chunks
Bidirectional Streaming Stream of requests Stream of responses Real-time chat, collaborative editing
service DataService {
  // Unary
  rpc GetUser (UserRequest) returns (User);

  // Server streaming — note "stream" before the return type
  rpc WatchPrices (TickerRequest) returns (stream PriceUpdate);

  // Client streaming — note "stream" before the request type
  rpc UploadChunks (stream FileChunk) returns (UploadResult);

  // Bidirectional streaming
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

Achieving bidirectional streaming with REST/HTTP/1.1 requires workarounds like WebSockets or Server-Sent Events — separate protocols with their own complexity. In gRPC, it's a first-class feature of the framework.

Interview Tip

Interviewers frequently ask: "When would you choose gRPC over REST?" The structured answer: choose gRPC for internal service-to-service communication where you control both client and server, need high throughput (millions of RPC/sec), require streaming, or want compile-time schema enforcement. Choose REST for public APIs (browser clients, third-party integrations), when human readability matters for debugging, or when clients can't use HTTP/2. A nuanced addition: gRPC-Web exists for browser clients but requires a proxy layer (like Envoy) and doesn't support all streaming modes — it's not a drop-in replacement for REST in browser contexts.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free