Chapter 7: The Four Communication Patterns
gRPC supports four distinct RPC patterns. This is one of its biggest advantages over REST (which only has request-response). Understanding when to use each pattern is crucial.
Overview
Pattern 1: Unary RPC
The simplest pattern. One request, one response. Like a normal function call.
// Proto definition
rpc GetUser(GetUserRequest) returns (User);
When to Use Unary
- Simple request-response (CRUD operations)
- When the response fits in a single message
- When latency is more important than throughput
- ~90% of RPCs in a typical system are unary
C++ Server Example
Status GetUser(ServerContext* context,
const GetUserRequest* request,
User* response) {
// Look up user
auto user = db_->FindUser(request->user_id());
if (!user) {
return Status(StatusCode::NOT_FOUND, "User not found");
}
*response = *user;
return Status::OK;
}
Pattern 2: Server Streaming
Client sends one request, server sends back a stream of responses. The server decides when to stop.
// Proto definition
rpc ListUsers(ListUsersRequest) returns (stream User);
rpc Subscribe(SubscribeRequest) returns (stream Event);
When to Use Server Streaming
- Large result sets — instead of one huge response, stream items one by one
- Real-time subscriptions — "notify me when something changes"
- Log/event tailing — stream log entries as they happen
- Download — stream file chunks
Think of gNMI telemetry streaming. A network device streams interface counters every second to a collector. That's server streaming: one Subscribe request, continuous stream of TelemetryUpdate responses.
C++ Server Example
Status ListUsers(ServerContext* context,
const ListUsersRequest* request,
ServerWriter<User>* writer) {
auto users = db_->GetAllUsers(request->filter());
for (const auto& user : users) {
// Check if client cancelled
if (context->IsCancelled()) {
return Status(StatusCode::CANCELLED, "Client cancelled");
}
writer->Write(user);
}
return Status::OK;
}
Pattern 3: Client Streaming
Client sends a stream of messages, server responds with a single message after receiving all of them.
// Proto definition
rpc UploadFile(stream FileChunk) returns (UploadStatus);
rpc RecordRoute(stream Point) returns (RouteSummary);
When to Use Client Streaming
- File upload — send file in chunks
- Batch operations — send many items, get one summary
- Sensor data — stream readings, get aggregated result
- IoT telemetry — device streams metrics to server
C++ Server Example
Status UploadFile(ServerContext* context,
ServerReader<FileChunk>* reader,
UploadStatus* response) {
FileChunk chunk;
int64_t total_bytes = 0;
while (reader->Read(&chunk)) {
// Process each chunk (write to disk, etc.)
total_bytes += chunk.data().size();
}
response->set_bytes_received(total_bytes);
response->set_status("OK");
return Status::OK;
}
Pattern 4: Bidirectional Streaming
Both client and server send streams of messages independently. The two streams are independent — the server doesn't have to wait for all client messages before responding.
// Proto definition
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
rpc RouteGuide(stream Point) returns (stream Feature);
When to Use Bidirectional Streaming
- Chat/messaging — both sides send messages freely
- Real-time collaboration — like Google Docs
- Game state sync — client sends inputs, server sends world updates
- Interactive processing — send data, get partial results as they're computed
C++ Server Example
Status Chat(ServerContext* context,
ServerReaderWriter<ChatMessage, ChatMessage>* stream) {
ChatMessage msg;
while (stream->Read(&msg)) {
// Echo back with modification (simple example)
ChatMessage reply;
reply.set_text("Server received: " + msg.text());
reply.set_timestamp(GetCurrentTime());
stream->Write(reply);
}
return Status::OK;
}
Choosing the Right Pattern
Real-World Examples by Pattern
| Pattern | Example | Why This Pattern |
|---|---|---|
| Unary | Get user profile | Simple lookup, small response |
| Unary | Create order | One action, one result |
| Server stream | Stock price ticker | Continuous updates from server |
| Server stream | Search results | Stream results as found |
| Client stream | File upload | Large data sent in chunks |
| Client stream | GPS tracking | Device sends location updates |
| Bidi stream | Chat application | Both sides send freely |
| Bidi stream | Voice call | Audio in both directions |
Streaming adds complexity: you need to handle partial failures, backpressure, reconnection, and ordering. If your data fits in a single response (< 4MB default max), use unary. Streaming is for when you genuinely need it — large datasets, real-time updates, or long-lived connections.
- Unary = 90% of RPCs. Simple request-response.
- Server streaming = server pushes multiple responses (subscriptions, large lists)
- Client streaming = client sends multiple messages, server responds once (uploads, batches)
- Bidirectional = both sides stream independently (chat, real-time sync)
- All four patterns use a single HTTP/2 stream — the difference is how many DATA frames each side sends